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
)]
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
1238 for obj
in mdl
.objects
:
1239 if obj
.name
== F
"{mdl.name}_phy":
1243 obj
.select_set(state
=True)
1244 for ms
in obj
.material_slots
:
1245 if ms
.material
!= None:
1246 if ms
.material
not in mat_dict
:
1247 mat_dict
[ms
.material
] = ms
.material
.name
1248 ms
.material
.name
= asset_uid(ms
.material
)
1249 ms
.material
.use_nodes
= False
1252 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1253 check_existing
=False,
1255 apply_unit_scale
=False,
1256 bake_space_transform
=False
1259 bpy
.ops
.object.select_all(action
='DESELECT')
1262 vphys
.select_set(state
=True)
1263 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1264 check_existing
=False,
1266 apply_unit_scale
=False,
1267 bake_space_transform
=False
1269 bpy
.ops
.object.select_all(action
='DESELECT')
1271 # Fix material names back to original
1272 for mat
in mat_dict
:
1273 mat
.name
= mat_dict
[mat
]
1274 mat
.use_nodes
= True
1276 layer
.hide_viewport
=prev_state
1279 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1280 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1281 #o.write(F'$scale .32\n')
1282 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1283 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1284 o
.write(F
'$staticprop\n')
1285 o
.write(F
'$origin {origin[0]} {origin[1]} {origin[2]}\n')
1288 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1290 o
.write(" $concave\n")
1293 o
.write(F
'$cdmaterials {project_name}\n')
1294 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1298 # Copy bsp file (and also lightpatch it)
1300 def cxr_patchmap( src
, dst
):
1301 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1302 shutil
.copyfile( src
, dst
)
1305 # Convexer operators
1306 # ------------------------------------------------------------------------------
1308 # Force reload of shared libraries
1310 class CXR_RELOAD(bpy
.types
.Operator
):
1311 bl_idname
="convexer.reload"
1313 def execute(_
,context
):
1317 # Used for exporting data to use with ASAN builds
1319 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1320 bl_idname
="convexer.dev_test"
1321 bl_label
="Export development data"
1323 def execute(_
,context
):
1324 # Prepare input data
1325 mesh_src
= mesh_cxr_format(context
.active_object
)
1326 libcxr_write_test_data
.call( pointer(mesh_src
) )
1329 # UI: Preview how the brushes will looks in 3D view
1331 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1332 bl_idname
="convexer.preview"
1333 bl_label
="Preview Brushes"
1338 def execute(_
,context
):
1341 def modal(_
,context
,event
):
1342 global cxr_view_mesh
1343 static
= _
.__class
__
1345 if event
.type == 'ESC':
1348 cxr_view_mesh
= None
1349 static
.RUNNING
= False
1354 return {'PASS_THROUGH'}
1356 def invoke(_
,context
,event
):
1357 global cxr_view_shader
, cxr_view_mesh
1358 static
= _
.__class
__
1359 static
.LASTERR
= None
1363 mesh_src
= mesh_cxr_format(context
.active_object
)
1366 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1369 cxr_view_mesh
= None
1373 static
.LASTERR
= ["There is no error", \
1379 "Non-Convex Polygon",\
1384 return {'CANCELLED'}
1386 context
.window_manager
.modal_handler_add(_
)
1387 return {'RUNNING_MODAL'}
1389 # Generate preview using cxr
1391 ptrpreview
= libcxr_world_preview
.call( world
)
1392 preview
= ptrpreview
[0]
1394 vertices
= preview
.vertices
[:preview
.vertex_count
]
1395 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1397 colours
= preview
.colours
[:preview
.vertex_count
]
1398 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1400 indices
= preview
.indices
[:preview
.indices_count
]
1401 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1402 for i
in range(int(preview
.indices_count
/3)) ]
1404 cxr_view_mesh
= batch_for_shader(
1405 cxr_view_shader
, 'TRIS',
1406 { "pos": vertices
, "color": colours
},
1410 libcxr_free_tri_mesh
.call( ptrpreview
)
1411 libcxr_free_world
.call( world
)
1415 # Allow user to spam the operator
1417 return {'CANCELLED'}
1419 if not static
.RUNNING
:
1420 static
.RUNNING
= True
1421 context
.window_manager
.modal_handler_add(_
)
1422 return {'RUNNING_MODAL'}
1424 # Search for VMF compiler executables in subdirectory
1426 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1427 bl_idname
="convexer.detect_compilers"
1428 bl_label
="Find compilers"
1430 def execute(self
,context
):
1431 scene
= context
.scene
1432 settings
= scene
.cxr_data
1433 subdir
= settings
.subdir
1435 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1436 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1437 if os
.path
.exists(searchpath
):
1438 settings
[F
'exe_{exename}'] = searchpath
1442 # Main compile function
1444 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1445 bl_idname
="convexer.chain"
1446 bl_label
="Compile Chain"
1461 def cancel(_
,context
):
1462 global cxr_jobs_batch
1463 static
= _
.__class
__
1464 wm
= context
.window_manager
1466 if static
.SUBPROC
!= None:
1467 static
.SUBPROC
.terminate()
1468 static
.SUBPROC
= None
1470 if static
.TIMER
!= None:
1471 wm
.event_timer_remove( static
.TIMER
)
1476 cxr_jobs_batch
= None
1480 def modal(_
,context
,ev
):
1481 static
= _
.__class
__
1483 if ev
.type == 'TIMER':
1484 global cxr_jobs_batch
1486 if static
.WAIT_REDRAW
:
1488 return {'PASS_THROUGH'}
1489 static
.WAIT_REDRAW
= True
1491 if static
.USER_EXIT
:
1492 print( "Chain USER_EXIT" )
1493 return _
.cancel(context
)
1495 if static
.SUBPROC
!= None:
1496 # Deal with async modes
1497 status
= static
.SUBPROC
.poll()
1500 # Cannot redirect STDOUT through here without causing
1501 # undefined behaviour due to the Blender Python specification.
1503 # Have to write it out to a file and read it back in.
1505 with
open("/tmp/convexer_compile_log.txt","r") as log
:
1506 static
.LOG
= log
.readlines()
1507 return {'PASS_THROUGH'}
1509 #for l in static.SUBPROC.stdout:
1510 # print( F'-> {l.decode("utf-8")}',end='' )
1511 static
.SUBPROC
= None
1514 print(F
'Compiler () error: {status}')
1515 return _
.cancel(context
)
1517 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1518 cxr_jobs_update_graph( static
.JOBINFO
)
1520 return {'PASS_THROUGH'}
1522 # Compile syncronous thing
1523 for sys
in static
.JOBINFO
:
1524 for i
,target
in enumerate(sys
['jobs']):
1527 if callable(sys
['exec']):
1528 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1530 if not sys
['exec'](*target
):
1531 print( "Job failed" )
1532 return _
.cancel(context
)
1534 sys
['jobs'][i
] = None
1537 # Run external executable (wine)
1538 static
.SUBPROC
= subprocess
.Popen( target
,
1539 stdout
=static
.FILE
,\
1540 stderr
=subprocess
.PIPE
,\
1545 cxr_jobs_update_graph( static
.JOBINFO
)
1547 return {'PASS_THROUGH'}
1550 print( "All jobs completed!" )
1551 cxr_jobs_batch
= None
1554 return _
.cancel(context
)
1556 return {'PASS_THROUGH'}
1558 def invoke(_
,context
,event
):
1559 static
= _
.__class
__
1560 wm
= context
.window_manager
1562 if static
.TIMER
== None:
1563 print("Launching compiler toolchain")
1565 # Run static compilation units now (collect, vmt..)
1566 filepath
= bpy
.data
.filepath
1567 directory
= os
.path
.dirname(filepath
)
1568 settings
= bpy
.context
.scene
.cxr_data
1570 asset_dir
= F
"{directory}/modelsrc"
1571 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1572 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1573 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1575 os
.makedirs( asset_dir
, exist_ok
=True )
1576 os
.makedirs( material_dir
, exist_ok
=True )
1577 os
.makedirs( model_dir
, exist_ok
=True )
1579 static
.FILE
= open(F
"/tmp/convexer_compile_log.txt","w")
1582 sceneinfo
= cxr_scene_collect()
1588 for brush
in sceneinfo
['geo']:
1589 for ms
in brush
['object'].material_slots
:
1590 a_materials
.add( ms
.material
)
1591 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1592 errmat
= ms
.material
.name
1593 errnam
= brush
['object'].name
1594 print( F
"Vertex shader {errmat} used on {errnam}")
1595 return {'CANCELLED'}
1597 for ent
in sceneinfo
['entities']:
1598 if isinstance(ent
['object'],bpy
.types
.Collection
): continue
1600 if ent
['object'].type == 'MESH':
1601 for ms
in ent
['object'].material_slots
:
1602 a_materials
.add( ms
.material
)
1604 # TODO.. this should just be in the entity loop
1605 for hero
in sceneinfo
['heros']:
1606 uid
= asset_uid(hero
['collection'])
1607 qc_jobs
+= [F
'{uid}.qc']
1608 for obj
in hero
['collection'].objects
:
1609 for ms
in obj
.material_slots
:
1610 a_materials
.add( ms
.material
)
1611 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1612 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1614 errmat
= ms
.material
.name
1616 print( F
"Lightmapped shader {errmat} used on {errnam}")
1617 return {'CANCELLED'}
1620 for mat
in a_materials
:
1621 for pair
in compile_material(mat
):
1626 if isinstance(prop
,bpy
.types
.Image
):
1628 if 'flags' in pdef
: flags
= pdef
['flags']
1629 if prop
not in image_jobs
:
1630 image_jobs
+= [(prop
,)]
1631 prop
.cxr_data
.flags
= flags
1637 if settings
.comp_vmf
:
1638 static
.JOBINFO
+= [{
1639 "title": "Convexer",
1641 "colour": (1.0,0.3,0.1,1.0),
1642 "exec": cxr_export_vmf
,
1643 "jobs": [(sceneinfo
,output_vmf
)]
1646 if settings
.comp_textures
:
1647 if len(image_jobs
) > 0:
1648 static
.JOBINFO
+= [{
1649 "title": "Textures",
1651 "colour": (0.1,1.0,0.3,1.0),
1652 "exec": compile_image
,
1656 game
= 'z:'+settings
.subdir
.replace('/','\\')
1658 '-game', game
, settings
.project_name
1662 if settings
.comp_models
:
1663 if len(sceneinfo
['heros']) > 0:
1664 static
.JOBINFO
+= [{
1667 "colour": (0.5,0.5,1.0,1.0),
1668 "exec": cxr_export_modelsrc
,
1669 "jobs": [(h
['collection'], h
['origin'], asset_dir
, \
1670 settings
.project_name
, h
['transform']) for h
in \
1674 if len(qc_jobs
) > 0:
1675 static
.JOBINFO
+= [{
1676 "title": "StudioMDL",
1678 "colour": (0.8,0.1,0.1,1.0),
1679 "exec": "studiomdl",
1680 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1681 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1686 if settings
.comp_compile
:
1687 static
.JOBINFO
+= [{
1690 "colour": (0.1,0.2,1.0,1.0),
1692 "jobs": [[settings
[F
'exe_vbsp']] + args
],
1696 static
.JOBINFO
+= [{
1699 "colour": (0.9,0.5,0.5,1.0),
1701 "jobs": [[settings
[F
'exe_vvis']] + ['-fast'] + args
],
1705 vrad_opt
= settings
.opt_vrad
.split()
1706 static
.JOBINFO
+= [{
1709 "colour": (0.9,0.2,0.3,1.0),
1711 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
1715 static
.JOBINFO
+= [{
1718 "colour": (0.0,1.0,0.4,1.0),
1719 "exec": cxr_patchmap
,
1720 "jobs": [(F
"{directory}/{settings.project_name}.bsp",\
1721 F
"{settings.subdir}/maps/{settings.project_name}.bsp")]
1724 static
.USER_EXIT
=False
1725 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
1726 wm
.modal_handler_add(_
)
1728 cxr_jobs_update_graph( static
.JOBINFO
)
1730 return {'RUNNING_MODAL'}
1732 print("Chain exiting...")
1733 static
.USER_EXIT
=True
1734 return {'RUNNING_MODAL'}
1736 class CXR_RESET_HASHES(bpy
.types
.Operator
):
1737 bl_idname
="convexer.hash_reset"
1738 bl_label
="Reset asset hashes"
1740 def execute(_
,context
):
1741 for c
in bpy
.data
.collections
:
1742 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1743 c
.cxr_data
.asset_id
=0
1745 for t
in bpy
.data
.images
:
1746 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1747 t
.cxr_data
.asset_id
=0
1752 # ------------------------------------------------------------------------------
1754 # Helper buttons for 3d toolbox view
1756 class CXR_VIEW3D( bpy
.types
.Panel
):
1757 bl_idname
= "VIEW3D_PT_convexer"
1758 bl_label
= "Convexer"
1759 bl_space_type
= 'VIEW_3D'
1760 bl_region_type
= 'UI'
1761 bl_category
= "Convexer"
1764 def poll(cls
, context
):
1765 return (context
.object is not None)
1767 def draw(_
, context
):
1771 row
.operator("convexer.preview")
1773 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
1775 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
1777 # Main scene properties interface, where all the settings go
1779 class CXR_INTERFACE(bpy
.types
.Panel
):
1781 bl_idname
="SCENE_PT_convexer"
1782 bl_space_type
='PROPERTIES'
1783 bl_region_type
='WINDOW'
1786 def draw(_
,context
):
1787 _
.layout
.operator("convexer.reload")
1788 _
.layout
.operator("convexer.dev_test")
1789 _
.layout
.operator("convexer.preview")
1790 _
.layout
.operator("convexer.hash_reset")
1792 settings
= context
.scene
.cxr_data
1794 _
.layout
.prop(settings
, "debug")
1795 _
.layout
.prop(settings
, "scale_factor")
1796 _
.layout
.prop(settings
, "lightmap_scale")
1797 _
.layout
.prop(settings
, "light_scale" )
1798 _
.layout
.prop(settings
, "image_quality" )
1800 box
= _
.layout
.box()
1802 box
.prop(settings
, "project_name")
1803 box
.prop(settings
, "subdir")
1805 box
= _
.layout
.box()
1806 box
.operator("convexer.detect_compilers")
1807 box
.prop(settings
, "exe_studiomdl")
1808 box
.prop(settings
, "exe_vbsp")
1809 box
.prop(settings
, "exe_vvis")
1810 box
.prop(settings
, "exe_vrad")
1811 box
.prop(settings
, "opt_vrad")
1815 row
.prop(settings
,"comp_vmf")
1816 row
.prop(settings
,"comp_textures")
1817 row
.prop(settings
,"comp_models")
1818 row
.prop(settings
,"comp_compile")
1820 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
1823 row
.operator("convexer.chain", text
=text
)
1826 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1827 bl_label
="VMT Properties"
1828 bl_idname
="SCENE_PT_convexer_vmt"
1829 bl_space_type
='PROPERTIES'
1830 bl_region_type
='WINDOW'
1831 bl_context
="material"
1833 def draw(_
,context
):
1834 active_object
= bpy
.context
.active_object
1835 if active_object
== None: return
1837 active_material
= active_object
.active_material
1838 if active_material
== None: return
1840 properties
= active_material
.cxr_data
1841 info
= material_info( active_material
)
1843 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1844 _
.layout
.prop( properties
, "shader" )
1847 _
.layout
.label(text
=F
"{xk}:={info[xk]}")
1849 def _mtex( name
, img
, uiParent
):
1852 box
= uiParent
.box()
1853 box
.label( text
=F
'{name} "{img.filepath}"' )
1855 if ((x
& (x
- 1)) == 0):
1858 closest_diff
= 10000000
1860 dist
= abs((1 << i
)-x
)
1861 if dist
< closest_diff
:
1866 return 1 << (closest
+1)
1868 return 1 << (closest
-1)
1873 row
.prop( img
.cxr_data
, "export_res" )
1874 row
.prop( img
.cxr_data
, "fmt" )
1877 row
.prop( img
.cxr_data
, "mipmap" )
1878 row
.prop( img
.cxr_data
, "lod" )
1879 row
.prop( img
.cxr_data
, "clamp" )
1881 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1882 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1884 def _mview( layer
, uiParent
):
1888 if isinstance(layer
[decl
],dict): # $property definition
1890 ptype
= pdef
['type']
1896 if ('shaders' in pdef
) and \
1897 (properties
.shader
not in pdef
['shaders']):
1900 if ptype
== 'intrinsic':
1901 if decl
not in info
:
1906 if isinstance(pdef
[ch
],dict):
1907 if ptype
== 'ui' or ptype
== 'intrinsic':
1909 elif getattr(properties
,decl
) == pdef
['default']:
1912 thisnode
= uiParent
.box()
1916 thisnode
.label( text
=decl
)
1917 elif ptype
== 'intrinsic':
1918 if isinstance(info
[decl
], bpy
.types
.Image
):
1919 _mtex( decl
, info
[decl
], thisnode
)
1921 # hidden intrinsic value.
1922 # Means its a float array or something not an image
1923 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
1925 thisnode
.prop(properties
,decl
)
1926 if expandview
: _mview(pdef
,thisnode
)
1928 _mview( cxr_shader_params
, _
.layout
)
1930 def cxr_entity_changeclass(_
,context
):
1931 active_object
= context
.active_object
1933 # Create ID properties
1935 classname
= cxr_custom_class(active_object
)
1937 if classname
in cxr_entities
:
1938 entdef
= cxr_entities
[classname
]
1940 kvs
= entdef
['keyvalues']
1941 if callable(kvs
): kvs
= kvs(active_object
)
1947 if callable(kv
) or not isinstance(kv
,dict): continue
1949 if key
not in active_object
:
1950 active_object
[key
] = kv
['default']
1951 id_prop
= active_object
.id_properties_ui(key
)
1952 id_prop
.update(default
=kv
['default'])
1954 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
1955 bl_label
="Entity Config"
1956 bl_idname
="SCENE_PT_convexer_entity"
1957 bl_space_type
='PROPERTIES'
1958 bl_region_type
='WINDOW'
1961 def draw(_
,context
):
1962 active_object
= bpy
.context
.active_object
1964 if active_object
== None: return
1967 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
1971 ecn
= cxr_intrinsic_classname( active_object
)
1972 classname
= cxr_custom_class( active_object
)
1975 if active_object
.type == 'MESH':
1976 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
1977 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
1979 if classname
== 'NONE':
1982 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
1983 _
.layout
.enabled
=False
1986 kvs
= cxr_entity_keyvalues( {
1987 "object": active_object
,
1988 "transform": default_context
,
1989 "classname": classname
1995 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
1997 row
= _
.layout
.row()
1999 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2001 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2003 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2004 bl_label
= "Source Settings"
2005 bl_idname
= "LIGHT_PT_cxr"
2006 bl_space_type
= 'PROPERTIES'
2007 bl_region_type
= 'WINDOW'
2010 def draw(self
, context
):
2011 layout
= self
.layout
2012 scene
= context
.scene
2014 active_object
= bpy
.context
.active_object
2015 if active_object
== None: return
2017 if active_object
.type == 'LIGHT' or \
2018 active_object
.type == 'LIGHT_PROBE':
2020 properties
= active_object
.data
.cxr_data
2022 if active_object
.type == 'LIGHT':
2023 layout
.prop( properties
, "realtime" )
2024 elif active_object
.type == 'LIGHT_PROBE':
2025 layout
.prop( properties
, "size" )
2028 # ------------------------------------------------------------------------------
2030 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2031 export_res
: bpy
.props
.IntVectorProperty(
2033 description
="Texture Export Resolution",
2039 fmt
: bpy
.props
.EnumProperty(
2042 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2043 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2044 ('RGB', "RGB", "Uncompressed", '', 2),
2045 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2047 description
="Image format",
2050 last_hash
: bpy
.props
.StringProperty( name
="" )
2051 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2053 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2054 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2055 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2056 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2058 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2059 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2061 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2062 size
: bpy
.props
.EnumProperty(
2065 ('1',"1x1",'','',0),
2066 ('2',"2x2",'','',1),
2067 ('3',"4x4",'','',2),
2068 ('4',"8x8",'','',3),
2069 ('5',"16x16",'','',4),
2070 ('6',"32x32",'','',5),
2071 ('7',"64x64",'','',6),
2072 ('8',"128x128",'','',7),
2073 ('9',"256x256",'','',8)
2075 description
="Texture resolution",
2078 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2079 entity
: bpy
.props
.BoolProperty(name
="")
2081 enum_pointents
= [('NONE',"None","")]
2082 enum_brushents
= [('NONE',"None","")]
2084 for classname
in cxr_entities
:
2085 entdef
= cxr_entities
[classname
]
2086 if 'allow' in entdef
:
2087 itm
= [(classname
, classname
, "")]
2088 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2089 else: enum_brushents
+= itm
2091 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2092 update
=cxr_entity_changeclass
, default
='NONE' )
2094 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2095 update
=cxr_entity_changeclass
, default
='NONE' )
2097 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2098 last_hash
: bpy
.props
.StringProperty( name
="" )
2099 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2101 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2102 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2103 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
2105 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2106 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2107 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2108 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2109 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2110 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2111 opt_vrad
: bpy
.props
.StringProperty( name
="args" )
2113 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
2114 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2115 default
=32.0,min=1.0)
2116 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2117 default
=1.0,min=0.01)
2119 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2120 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2121 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2123 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2125 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2126 default
=8, min=0, max=18 )
2128 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2129 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2130 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2131 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2133 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2134 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2135 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2136 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2137 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2138 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
]
2140 vmt_param_dynamic_class
= None
2143 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2146 bpy
.utils
.register_class(c
)
2148 # Build dynamic VMT properties class defined by cxr_shader_params
2149 annotations_dict
= {}
2151 def _dvmt_propogate(layer
):
2152 nonlocal annotations_dict
2155 if isinstance(layer
[decl
],dict): # $property definition
2159 if pdef
['type'] == 'bool':
2160 prop
= bpy
.props
.BoolProperty(\
2161 name
= pdef
['name'],\
2162 default
= pdef
['default'])
2164 elif pdef
['type'] == 'float':
2165 prop
= bpy
.props
.FloatProperty(\
2166 name
= pdef
['name'],\
2167 default
= pdef
['default'])
2169 elif pdef
['type'] == 'vector':
2170 if 'subtype' in pdef
:
2171 prop
= bpy
.props
.FloatVectorProperty(\
2172 name
= pdef
['name'],\
2173 subtype
= pdef
['subtype'],\
2174 default
= pdef
['default'],\
2175 size
= len(pdef
['default']))
2177 prop
= bpy
.props
.FloatVectorProperty(\
2178 name
= pdef
['name'],\
2179 default
= pdef
['default'],\
2180 size
= len(pdef
['default']))
2182 elif pdef
['type'] == 'string':
2183 prop
= bpy
.props
.StringProperty(\
2184 name
= pdef
['name'],\
2185 default
= pdef
['default'])
2187 elif pdef
['type'] == 'enum':
2188 prop
= bpy
.props
.EnumProperty(\
2189 name
= pdef
['name'],\
2190 items
= pdef
['items'],\
2191 default
= pdef
['default'])
2194 annotations_dict
[decl
] = prop
2196 # Recurse into sub-definitions
2197 _dvmt_propogate(pdef
)
2199 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2202 cxr_shaders
[_
]["name"],\
2203 '') for _
in cxr_shaders
],\
2204 default
= next(iter(cxr_shaders
)))
2206 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2209 _dvmt_propogate( cxr_shader_params
)
2210 vmt_param_dynamic_class
= type(
2212 (bpy
.types
.PropertyGroup
,),{
2213 "__annotations__": annotations_dict
2217 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2220 bpy
.types
.Material
.cxr_data
= \
2221 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2222 bpy
.types
.Image
.cxr_data
= \
2223 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2224 bpy
.types
.Object
.cxr_data
= \
2225 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2226 bpy
.types
.Collection
.cxr_data
= \
2227 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2228 bpy
.types
.Light
.cxr_data
= \
2229 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2230 bpy
.types
.LightProbe
.cxr_data
= \
2231 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2232 bpy
.types
.Scene
.cxr_data
= \
2233 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2235 # CXR Scene settings
2238 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2239 cxr_draw
,(),'WINDOW','POST_VIEW')
2241 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2242 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2244 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2245 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2248 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2250 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2252 bpy
.utils
.unregister_class(c
)
2254 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2255 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2257 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2258 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')