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
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)
85 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
86 blf
.position(0,2,80,0)
88 blf
.color(0,1.0,0.2,0.2,0.9)
89 blf
.draw(0,"This is a stoopid error\nWIthiuawdnaw")
91 # Something is off with TIMER,
92 # this forces the viewport to redraw before we can continue with our
95 CXR_COMPILER_CHAIN
.WAIT_REDRAW
= False
98 global cxr_view_shader
, cxr_view_mesh
, cxr_view_lines
100 cxr_view_shader
.bind()
102 gpu
.state
.depth_mask_set(False)
103 gpu
.state
.line_width_set(1.5)
104 gpu
.state
.face_culling_set('BACK')
105 gpu
.state
.depth_test_set('NONE')
106 gpu
.state
.blend_set('ALPHA')
108 if cxr_view_lines
!= None:
109 cxr_view_lines
.draw( cxr_view_shader
)
111 gpu
.state
.depth_test_set('LESS_EQUAL')
112 gpu
.state
.blend_set('ADDITIVE')
113 if cxr_view_mesh
!= None:
114 cxr_view_mesh
.draw( cxr_view_shader
)
116 def cxr_jobs_update_graph(jobs
):
117 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
127 total_width
+= sys
['w']
136 colour
= sys
['colour']
137 colourwait
= (colour
[0],colour
[1],colour
[2],0.4)
138 colourrun
= (colour
[0]*1.5,colour
[1]*1.5,colour
[2]*1.5,0.5)
139 colourdone
= (colour
[0],colour
[1],colour
[2],1.0)
142 sfsub
= (1.0/(len(jobs
)))*w
146 if j
== None: colour
= colourdone
147 else: colour
= colourwait
149 px
= (cur
+ (i
)*sfsub
) * sf
150 px1
= (cur
+ (i
+1.0)*sfsub
) * sf
- 0.003
153 verts
+= [(px
,0), (px
, h
), (px1
, 0.0), (px1
,h
)]
154 colours
+= [colour
,colour
,colour
,colour
]
155 indices
+= [(ci
+0,ci
+2,ci
+3),(ci
+0,ci
+3,ci
+1)]
158 cxr_jobs_inf
+= [((sf
*cur
), sys
['title'])]
161 cxr_jobs_batch
= batch_for_shader(
162 cxr_ui_shader
, 'TRIS',
163 { "aPos": verts
, "aColour": colours
},
167 # view_layer.update() doesnt seem to work,
168 # tag_redraw() seems to have broken
169 # therefore, change a property
171 ob
= bpy
.context
.scene
.objects
[0]
172 ob
.hide_render
= ob
.hide_render
174 # the 'real' way to refresh the scene
175 for area
in bpy
.context
.window
.screen
.areas
:
176 if area
.type == 'view_3d':
180 # ------------------------------------------------------------------------------
182 # dlclose for reloading modules manually
184 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
185 libc_dlclose
.argtypes
= [c_void_p
]
187 # wrapper for ctypes binding
189 def __init__(_
,name
,argtypes
,restype
):
191 _
.argtypes
= argtypes
196 _
.call
= getattr(so
,_
.name
)
197 _
.call
.argtypes
= _
.argtypes
199 if _
.restype
!= None:
200 _
.call
.restype
= _
.restype
203 # ------------------------------------------------------------------------------
207 # Structure definitions
209 class cxr_edge(Structure
):
210 _fields_
= [("i0",c_int32
),
212 ("freestyle",c_int32
)]
214 class cxr_static_loop(Structure
):
215 _fields_
= [("index",c_int32
),
216 ("edge_index",c_int32
),
219 class cxr_polygon(Structure
):
220 _fields_
= [("loop_start",c_int32
),
221 ("loop_total",c_int32
),
222 ("normal",c_double
* 3),
223 ("center",c_double
* 3),
224 ("material_id",c_int32
)]
226 class cxr_material(Structure
):
227 _fields_
= [("res",c_int32
* 2),
230 class cxr_static_mesh(Structure
):
231 _fields_
= [("vertices",POINTER(c_double
* 3)),
232 ("edges",POINTER(cxr_edge
)),
233 ("loops",POINTER(cxr_static_loop
)),
234 ("polys",POINTER(cxr_polygon
)),
235 ("materials",POINTER(cxr_material
)),
237 ("poly_count",c_int32
),
238 ("vertex_count",c_int32
),
239 ("edge_count",c_int32
),
240 ("loop_count",c_int32
),
241 ("material_count",c_int32
)]
243 class cxr_tri_mesh(Structure
):
244 _fields_
= [("vertices",POINTER(c_double
*3)),
245 ("colours",POINTER(c_double
*4)),
246 ("indices",POINTER(c_int32
)),
247 ("indices_count",c_int32
),
248 ("vertex_count",c_int32
)]
250 class cxr_vmf_context(Structure
):
251 _fields_
= [("mapversion",c_int32
),
252 ("skyname",c_char_p
),
253 ("detailvbsp",c_char_p
),
254 ("detailmaterial",c_char_p
),
256 ("offset",c_double
*3),
257 ("lightmap_scale",c_int32
),
258 ("brush_count",c_int32
),
259 ("entity_count",c_int32
),
260 ("face_count",c_int32
)]
262 # Convert blenders mesh format into CXR's static format (they are very similar)
264 def mesh_cxr_format(obj
):
267 if bpy
.context
.active_object
!= None:
268 orig_state
= obj
.mode
269 if orig_state
!= 'OBJECT':
270 bpy
.ops
.object.mode_set(mode
='OBJECT')
272 dgraph
= bpy
.context
.evaluated_depsgraph_get()
273 data
= obj
.evaluated_get(dgraph
).data
275 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
277 mesh
= cxr_static_mesh()
279 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
280 for i
, vert
in enumerate(data
.vertices
):
281 v
= obj
.matrix_world
@ vert
.co
282 vertex_data
[i
][0] = c_double(v
[0])
283 vertex_data
[i
][1] = c_double(v
[1])
284 vertex_data
[i
][2] = c_double(v
[2])
286 loop_data
= (cxr_static_loop
*len(data
.loops
))()
287 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
289 for i
, poly
in enumerate(data
.polygons
):
290 loop_start
= poly
.loop_start
291 loop_end
= poly
.loop_start
+ poly
.loop_total
292 for loop_index
in range(loop_start
, loop_end
):
293 loop
= data
.loops
[loop_index
]
294 loop_data
[loop_index
].index
= loop
.vertex_index
295 loop_data
[loop_index
].edge_index
= loop
.edge_index
298 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
299 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
300 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
302 loop_data
[loop_index
].uv
[0] = c_double(0.0)
303 loop_data
[loop_index
].uv
[1] = c_double(0.0)
304 center
= obj
.matrix_world
@ poly
.center
305 normal
= mtx_rot
@ poly
.normal
307 polygon_data
[i
].loop_start
= poly
.loop_start
308 polygon_data
[i
].loop_total
= poly
.loop_total
309 polygon_data
[i
].normal
[0] = normal
[0]
310 polygon_data
[i
].normal
[1] = normal
[1]
311 polygon_data
[i
].normal
[2] = normal
[2]
312 polygon_data
[i
].center
[0] = center
[0]
313 polygon_data
[i
].center
[1] = center
[1]
314 polygon_data
[i
].center
[2] = center
[2]
315 polygon_data
[i
].material_id
= poly
.material_index
317 edge_data
= (cxr_edge
*len(data
.edges
))()
319 for i
, edge
in enumerate(data
.edges
):
320 edge_data
[i
].i0
= edge
.vertices
[0]
321 edge_data
[i
].i1
= edge
.vertices
[1]
322 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
324 material_data
= (cxr_material
*len(obj
.material_slots
))()
326 for i
, ms
in enumerate(obj
.material_slots
):
327 inf
= material_info(ms
.material
)
328 material_data
[i
].res
[0] = inf
['res'][0]
329 material_data
[i
].res
[1] = inf
['res'][1]
330 material_data
[i
].name
= inf
['name'].encode('utf-8')
332 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
333 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
334 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
335 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
336 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
338 mesh
.poly_count
= len(data
.polygons
)
339 mesh
.vertex_count
= len(data
.vertices
)
340 mesh
.edge_count
= len(data
.edges
)
341 mesh
.loop_count
= len(data
.loops
)
342 mesh
.material_count
= len(obj
.material_slots
)
344 if orig_state
!= None:
345 bpy
.ops
.object.mode_set(mode
=orig_state
)
349 # Callback ctypes indirection things.. not really sure.
350 c_libcxr_log_callback
= None
351 c_libcxr_line_callback
= None
354 # -------------------------------------------------------------
355 libcxr_decompose
= extern( "cxr_decompose",
356 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
359 libcxr_free_world
= extern( "cxr_free_world",
363 libcxr_write_test_data
= extern( "cxr_write_test_data",
364 [POINTER(cxr_static_mesh
)],
367 libcxr_world_preview
= extern( "cxr_world_preview",
369 POINTER(cxr_tri_mesh
)
371 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
375 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
376 [POINTER(cxr_vmf_context
), c_void_p
],
379 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
380 [POINTER(cxr_vmf_context
), c_void_p
],
383 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
384 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
387 libcxr_end_vmf
= extern( "cxr_end_vmf",
388 [POINTER(cxr_vmf_context
),c_void_p
],
392 # VDF + with open wrapper
393 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
394 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
395 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
396 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
397 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
398 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
400 class vdf_structure():
401 def __init__(_
,path
):
404 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
406 print( F
"Could not open file {_.path}" )
409 def __exit__(_
,type,value
,traceback
):
411 libcxr_vdf_close
.call(_
.fp
)
413 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
415 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
417 libcxr_vdf_edon
.call(_
.fp
)
419 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
422 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
424 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
425 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
426 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
427 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
428 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
429 libcxr_world_preview
, libcxr_free_tri_mesh
]
433 def libcxr_log_callback(logStr
):
434 print( F
"{logStr.decode('utf-8')}",end
='' )
436 cxr_line_positions
= None
437 cxr_line_colours
= None
439 def cxr_reset_lines():
440 global cxr_line_positions
, cxr_line_colours
442 cxr_line_positions
= []
443 cxr_line_colours
= []
445 def cxr_batch_lines():
446 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
448 cxr_view_lines
= batch_for_shader(\
449 cxr_view_shader
, 'LINES',\
450 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
452 def libcxr_line_callback( p0
,p1
,colour
):
453 global cxr_line_colours
, cxr_line_positions
455 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
456 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
457 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
458 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
461 # ------------------------------------------------------------------------------
466 NBVTF_IMAGE_FORMAT_RGBA8888
= 0
467 NBVTF_IMAGE_FORMAT_RGB888
= 2
468 NBVTF_IMAGE_FORMAT_DXT1
= 13
469 NBVTF_IMAGE_FORMAT_DXT5
= 15
470 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
471 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
472 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
473 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
474 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
476 libnbvtf_convert
= extern( "nbvtf_convert", \
477 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
480 libnbvtf_init
= extern( "nbvtf_init", [], None )
481 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
484 # --------------------------
487 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
489 # Unload libraries if existing
490 def _reload( lib
, path
):
492 _handle
= lib
._handle
493 for i
in range(10): libc_dlclose( _handle
)
496 return cdll
.LoadLibrary( F
'{os.path.dirname(__file__)}/{path}.so' )
498 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
499 libcxr
= _reload( libcxr
, "libcxr" )
501 for fd
in libnbvtf_funcs
:
502 fd
.loadfrom( libnbvtf
)
505 for fd
in libcxr_funcs
:
506 fd
.loadfrom( libcxr
)
509 global c_libcxr_log_callback
, c_libcxr_line_callback
511 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
512 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
514 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
515 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
516 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
518 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
519 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
521 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
522 print( F
"libcxr build time: {build_time.value}" )
527 # ------------------------------------------------------------------------------
529 # Standard entity functions, think of like base.fgd
531 def cxr_get_origin(obj
,context
):
532 return obj
.location
* context
['scale'] + mathutils
.Vector(context
['offset'])
534 def cxr_get_angles(obj
,context
):
535 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
542 def cxr_baseclass(classes
, other
):
545 base
.update(x
.copy())
548 # EEVEE Light component converter -> Source 1
550 def ent_lights(obj
,context
):
551 kvs
= cxr_baseclass([ent_origin
],\
553 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
554 "_light": [int(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
555 [int(obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
)],
556 "_lightHDR": '-1 -1 -1 1',
560 if obj
.data
.type == 'SPOT':
561 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
562 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
564 # Blenders spotlights are -z forward
565 # Source is +x, however, it seems to use a completely different system.
566 # Since we dont care about roll for spotlights, we just take the
567 # pitch and yaw via trig
569 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
570 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
572 kvs
['pitch'] = math
.asin(fwd
[2]) * 57.295779513
573 kvs
['angles'] = [ 0.0, math
.atan2(fwd
[1],fwd
[0]) * 57.295779513, 0.0 ]
574 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
577 # Blender's default has a much more 'nice'
579 kvs
['_linear_attn'] = 1.0
581 elif obj
.data
.type == 'POINT':
582 kvs
['_quadratic_attn'] = 1.0
583 kvs
['_linear_attn'] = 0.0
585 elif obj
.data
.type == 'SUN':
590 def ent_cubemap(obj
,context
):
591 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
593 ent_origin
= { "origin": cxr_get_origin
}
594 ent_angles
= { "angles": cxr_get_angles
}
595 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
597 #include the user config
598 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
600 # Blender state callbacks
601 # ------------------------------------------------------------------------------
604 def cxr_on_load(dummy
):
605 global cxr_view_lines
, cxr_view_mesh
607 cxr_view_lines
= None
611 def cxr_dgraph_update(scene
,dgraph
):
613 print( F
"Hallo {time.time()}" )
615 # Convexer compilation functions
616 # ------------------------------------------------------------------------------
618 # Asset path management
620 def asset_uid(asset
):
621 if isinstance(asset
,str):
624 # Create a unique ID string
625 base
= "ABCDEFGHIJKLMNOPQRSTUV"
626 v
= asset
.cxr_data
.asset_id
635 dig
.append( int( v
% len(base
) ) )
641 if bpy
.context
.scene
.cxr_data
.include_names
:
642 name
+= asset
.name
.replace('.','_')
646 # -> <project_name>/<asset_name>
647 def asset_name(asset
):
648 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
650 # -> <subdir>/<project_name>/<asset_name>
651 def asset_path(subdir
, asset
):
652 return F
"{subdir}/{asset_name(asset_uid(asset))}"
654 # -> <csgo>/<subdir>/<project_name>/<asset_name>
655 def asset_full_path(sdir
,asset
):
656 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
657 F
"{asset_path(sdir,asset_uid(asset))}"
659 # Entity functions / infos
660 # ------------------------
662 def cxr_intrinsic_classname(obj
):
663 if obj
.type == 'LIGHT':
665 'SPOT': "light_spot",
667 'SUN': "light_directional" }[ obj
.data
.type ]
669 elif obj
.type == 'LIGHT_PROBE':
671 elif obj
.type == 'EMPTY':
677 def cxr_custom_class(obj
):
678 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
679 else: custom_class
= obj
.cxr_data
.classname
683 def cxr_classname(obj
):
684 intr
= cxr_intrinsic_classname(obj
)
685 if intr
!= None: return intr
687 custom_class
= cxr_custom_class(obj
)
688 if custom_class
!= 'NONE':
694 # intinsic: (k, False, value)
695 # property: (k, True, value or default)
699 def cxr_entity_keyvalues(obj
,context
,classname
):
700 if classname
not in cxr_entities
: return None
704 entdef
= cxr_entities
[classname
]
705 kvs
= entdef
['keyvalues']
707 if callable(kvs
): kvs
= kvs(obj
, context
)
714 if isinstance(kv
,dict):
716 value
= obj
[ F
"cxrkv_{k}" ]
719 value
= kv(obj
,context
)
721 if isinstance(value
,mathutils
.Vector
):
722 value
= [_
for _
in value
]
724 result
+= [(k
, isprop
, value
)]
728 # Extract material information from shader graph data
730 def material_info(mat
):
732 info
['res'] = (512,512)
733 info
['name'] = 'tools/toolsnodraw'
735 if mat
== None or mat
.use_nodes
== False:
739 if mat
.cxr_data
.shader
== 'Builtin':
740 info
['name'] = mat
.name
744 info
['name'] = asset_name(mat
)
746 # Using the cxr_graph_mapping as a reference, go through the shader
747 # graph and gather all $props from it.
749 def _graph_read( node_def
, node
=None, depth
=0 ):
753 def _variant_apply( val
):
756 if isinstance( val
, str ):
759 for shader_variant
in val
:
760 if shader_variant
[0] == mat
.cxr_data
.shader
:
761 return shader_variant
[1]
765 _graph_read
.extracted
= []
767 for node_idname
in node_def
:
768 for n
in mat
.node_tree
.nodes
:
769 if n
.bl_idname
== node_idname
:
770 node_def
= node_def
[node_idname
]
774 for link
in node_def
:
775 if isinstance( node_def
[link
], dict ):
776 inputt
= node
.inputs
[link
]
777 inputt_def
= node_def
[link
]
781 # look for definitions for the connected node type
782 con
= inputt
.links
[0].from_node
784 for node_idname
in inputt_def
:
785 if con
.bl_idname
== node_idname
:
786 con_def
= inputt_def
[ node_idname
]
787 _graph_read( con_def
, con
, depth
+1 )
789 # No definition found! :(
790 # TODO: Make a warning for this?
793 if "default" in inputt_def
:
794 prop
= _variant_apply( inputt_def
['default'] )
795 info
[prop
] = inputt
.default_value
797 prop
= _variant_apply( node_def
[link
] )
798 info
[prop
] = getattr(node
,link
)
800 _graph_read(cxr_graph_mapping
)
802 if "$basetexture" in info
:
803 export_res
= info
['$basetexture'].cxr_data
.export_res
804 info
['res'] = (export_res
[0], export_res
[1])
808 # Prepares Scene into dictionary format
810 def cxr_scene_collect():
811 context
= bpy
.context
813 # Make sure all of our asset types have a unique ID
814 def _uid_prepare(objtype
):
820 if vs
.asset_id
in used_ids
:
823 id_max
= max(id_max
,vs
.asset_id
)
824 used_ids
+=[vs
.asset_id
]
825 for vs
in to_generate
:
828 _uid_prepare(bpy
.data
.materials
)
829 _uid_prepare(bpy
.data
.images
)
830 _uid_prepare(bpy
.data
.collections
)
833 "entities": [], # Everything with a classname
834 "geo": [], # All meshes without a classname
835 "heros": [] # Collections prefixed with mdl_
838 def _collect(collection
,transform
):
841 if collection
.name
.startswith('.'): return
842 if collection
.hide_render
: return
844 if collection
.name
.startswith('mdl_'):
845 sceneinfo
['heros'] += [{
846 "collection": collection
,
847 "transform": transform
851 for obj
in collection
.objects
:
852 if obj
.hide_get(): continue
854 classname
= cxr_classname( obj
)
856 if classname
!= None:
857 sceneinfo
['entities'] += [{
859 "classname": classname
,
860 "transform": transform
862 elif obj
.type == 'MESH':
863 sceneinfo
['geo'] += [{
865 "transform": transform
868 for c
in collection
.children
:
869 cxr_scene_collect( c
, transform
)
872 "scale": context
.scene
.cxr_data
.scale_factor
,
877 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
878 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
881 if 'main' in bpy
.data
.collections
:
882 _collect( bpy
.data
.collections
['main'], transform_main
)
884 if 'skybox' in bpy
.data
.collections
:
885 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
889 # Write VMF out to file (JOB HANDLER)
891 def cxr_export_vmf(sceneinfo
):
894 # Setup output and state
895 filepath
= bpy
.data
.filepath
896 directory
= os
.path
.dirname(filepath
)
897 settings
= bpy
.context
.scene
.cxr_data
899 asset_dir
= F
"{directory}/bin"
900 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
901 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
903 os
.makedirs( asset_dir
, exist_ok
=True )
904 os
.makedirs( material_dir
, exist_ok
=True )
905 os
.makedirs( model_dir
, exist_ok
=True )
909 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
911 with
vdf_structure(output_vmf
) as m
:
912 print( F
"Write: {output_vmf}" )
914 vmfinfo
= cxr_vmf_context()
915 vmfinfo
.mapversion
= 4
917 #TODO: These need to be in options...
918 vmfinfo
.skyname
= b
"sky_csgo_night02b"
919 vmfinfo
.detailvbsp
= b
"detail.vbsp"
920 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
921 vmfinfo
.lightmap_scale
= 12
923 vmfinfo
.brush_count
= 0
924 vmfinfo
.entity_count
= 0
925 vmfinfo
.face_count
= 0
927 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
929 def _buildsolid( cmd
):
932 baked
= mesh_cxr_format( cmd
['object'] )
933 world
= libcxr_decompose
.call( baked
, None )
938 vmfinfo
.scale
= cmd
['transform']['scale']
940 offset
= cmd
['transform']['offset']
941 vmfinfo
.offset
[0] = offset
[0]
942 vmfinfo
.offset
[1] = offset
[1]
943 vmfinfo
.offset
[2] = offset
[2]
945 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
946 libcxr_free_world
.call( world
)
951 for brush
in sceneinfo
['geo']:
952 if not _buildsolid( brush
):
957 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
960 for ent
in sceneinfo
['entities']:
962 ctx
= ent
['transform']
963 cls
= ent
['classname']
966 m
.kv( 'classname', cls
)
968 kvs
= cxr_entity_keyvalues( obj
, ctx
, cls
)
971 if isinstance(kv
[2], list):
972 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
973 else: m
.kv( kv
[0], str(kv
[2]) )
975 if obj
.type == 'MESH':
976 if not _buildsolid( ent
):
986 # COmpile image using NBVTF and hash it (JOB HANDLER)
988 def compile_image(img
):
992 name
= asset_name(img
)
993 src_path
= bpy
.path
.abspath(img
.filepath
)
995 dims
= img
.cxr_data
.export_res
997 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888
,
998 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
999 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1000 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1001 }[ img
.cxr_data
.fmt
]
1003 mipmap
= img
.cxr_data
.mipmap
1004 lod
= img
.cxr_data
.lod
1005 clamp
= img
.cxr_data
.clamp
1006 flags
= img
.cxr_data
.flags
1008 q
=bpy
.context
.scene
.cxr_data
.image_quality
1010 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1011 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1012 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1014 if img
.cxr_data
.last_hash
!= comphash
:
1015 print( F
"Texture update: {img.filepath}" )
1017 src
= src_path
.encode('utf-8')
1018 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1022 # texture setting flags
1023 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1025 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1026 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1028 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1029 img
.cxr_data
.last_hash
= comphash
1034 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1037 def compile_material(mat
):
1038 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1040 info
= material_info(mat
)
1041 properties
= mat
.cxr_data
1045 # Walk the property tree
1046 def _mlayer( layer
):
1047 nonlocal properties
, props
1050 if isinstance(layer
[decl
],dict): # $property definition
1052 ptype
= pdef
['type']
1058 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1061 # Group expansion (does it have subdefinitions?)
1063 if isinstance(pdef
[ch
],dict):
1072 if ptype
== 'intrinsic':
1076 prop
= getattr(properties
,decl
)
1077 default
= pdef
['default']
1079 if not isinstance(prop
,str) and \
1080 not isinstance(prop
,bpy
.types
.Image
) and \
1081 hasattr(prop
,'__getitem__'):
1082 prop
= tuple([p
for p
in prop
])
1086 props
+= [(decl
,pdef
,prop
)]
1091 if expandview
: _mlayer(pdef
)
1093 _mlayer( cxr_shader_params
)
1096 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1097 vmt
.node( properties
.shader
)
1098 vmt
.put( "// Convexer export\n" )
1107 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1110 if isinstance(prop
,bpy
.types
.Image
):
1111 vmt
.kv( decl
, asset_name(prop
))
1112 elif isinstance(prop
,bool):
1113 vmt
.kv( decl
, '1' if prop
else '0' )
1114 elif isinstance(prop
,str):
1115 vmt
.kv( decl
, prop
)
1116 elif isinstance(prop
,float) or isinstance(prop
,int):
1117 vmt
.kv( decl
, _numeric(prop
) )
1118 elif isinstance(prop
,tuple):
1119 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1121 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1126 # Convexer operators
1127 # ------------------------------------------------------------------------------
1129 # Force reload of shared libraries
1131 class CXR_RELOAD(bpy
.types
.Operator
):
1132 bl_idname
="convexer.reload"
1134 def execute(_
,context
):
1138 # Used for exporting data to use with ASAN builds
1140 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1141 bl_idname
="convexer.dev_test"
1142 bl_label
="Export development data"
1144 def execute(_
,context
):
1145 # Prepare input data
1146 mesh_src
= mesh_cxr_format(context
.active_object
)
1147 libcxr_write_test_data
.call( pointer(mesh_src
) )
1150 # UI: Preview how the brushes will looks in 3D view
1152 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1153 bl_idname
="convexer.preview"
1154 bl_label
="Preview Brushes"
1159 def execute(_
,context
):
1162 def modal(_
,context
,event
):
1163 global cxr_view_mesh
1164 static
= _
.__class
__
1166 if event
.type == 'ESC':
1169 cxr_view_mesh
= None
1170 static
.RUNNING
= False
1175 return {'PASS_THROUGH'}
1177 def invoke(_
,context
,event
):
1178 global cxr_view_shader
, cxr_view_mesh
1179 static
= _
.__class
__
1180 static
.LASTERR
= None
1184 mesh_src
= mesh_cxr_format(context
.active_object
)
1187 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1190 cxr_view_mesh
= None
1194 static
.LASTERR
= ["There is no error", \
1200 "Non-Convex Polygon"]\
1204 return {'CANCELLED'}
1206 context
.window_manager
.modal_handler_add(_
)
1207 return {'RUNNING_MODAL'}
1209 # Generate preview using cxr
1211 ptrpreview
= libcxr_world_preview
.call( world
)
1212 preview
= ptrpreview
[0]
1214 vertices
= preview
.vertices
[:preview
.vertex_count
]
1215 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1217 colours
= preview
.colours
[:preview
.vertex_count
]
1218 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1220 indices
= preview
.indices
[:preview
.indices_count
]
1221 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1222 for i
in range(int(preview
.indices_count
/3)) ]
1224 cxr_view_mesh
= batch_for_shader(
1225 cxr_view_shader
, 'TRIS',
1226 { "pos": vertices
, "color": colours
},
1230 libcxr_free_tri_mesh
.call( ptrpreview
)
1231 libcxr_free_world
.call( world
)
1235 # Allow user to spam the operator
1237 return {'CANCELLED'}
1239 if not static
.RUNNING
:
1240 static
.RUNNING
= True
1241 context
.window_manager
.modal_handler_add(_
)
1242 return {'RUNNING_MODAL'}
1244 # Search for VMF compiler executables in subdirectory
1246 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1247 bl_idname
="convexer.detect_compilers"
1248 bl_label
="Find compilers"
1250 def execute(self
,context
):
1251 scene
= context
.scene
1252 settings
= scene
.cxr_data
1253 subdir
= settings
.subdir
1255 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1256 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1257 if os
.path
.exists(searchpath
):
1258 settings
[F
'exe_{exename}'] = searchpath
1262 # Main compile function
1264 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1265 bl_idname
="convexer.chain"
1266 bl_label
="Compile Chain"
1278 def cancel(_
,context
):
1279 static
= _
.__class
__
1280 wm
= context
.window_manager
1282 if static
.SUBPROC
!= None:
1283 static
.SUBPROC
.terminate()
1284 static
.SUBPROC
= None
1286 if static
.TIMER
!= None:
1287 wm
.event_timer_remove( static
.TIMER
)
1293 def modal(_
,context
,ev
):
1294 static
= _
.__class
__
1296 if ev
.type == 'TIMER':
1297 if static
.WAIT_REDRAW
:
1298 return {'PASS_THROUGH'}
1299 static
.WAIT_REDRAW
= True
1301 if static
.USER_EXIT
:
1302 print( "Chain USER_EXIT" )
1303 return _
.cancel(context
)
1305 if static
.SUBPROC
!= None:
1306 # Deal with async modes
1309 # Compile syncronous thing
1310 for sys
in static
.JOBINFO
:
1311 for i
,target
in enumerate(sys
['jobs']):
1313 print( F
"Start job: {static.JOBID} @{time.time()}" )
1315 if not sys
['exec'](target
):
1316 print( "Job failed" )
1317 return _
.cancel(context
)
1319 sys
['jobs'][i
] = None
1322 cxr_jobs_update_graph( static
.JOBINFO
)
1324 return {'PASS_THROUGH'}
1327 print( "All jobs completed!" )
1328 global cxr_jobs_batch
1329 cxr_jobs_batch
= None
1332 return _
.cancel(context
)
1334 return {'PASS_THROUGH'}
1336 def invoke(_
,context
,event
):
1337 static
= _
.__class
__
1338 wm
= context
.window_manager
1340 if static
.TIMER
== None:
1341 print("Launching compiler toolchain")
1343 # Run static compilation units now (collect, vmt..)
1344 sceneinfo
= cxr_scene_collect()
1349 for brush
in sceneinfo
['geo']:
1350 for ms
in brush
['object'].material_slots
:
1351 a_materials
.add( ms
.material
)
1354 for mat
in a_materials
:
1355 for pair
in compile_material(mat
):
1360 if isinstance(prop
,bpy
.types
.Image
):
1362 if 'flags' in pdef
: flags
= pdef
['flags']
1363 if prop
not in image_jobs
:
1364 image_jobs
+= [prop
]
1365 prop
.cxr_data
.flags
= flags
1371 static
.JOBINFO
+= [{
1372 "title": "Convexer",
1374 "colour": (1.0,0.3,0.1,1.0),
1375 "exec": cxr_export_vmf
,
1379 if len(image_jobs
) > 0:
1380 static
.JOBINFO
+= [{
1381 "title": "Textures",
1383 "colour": (0.1,1.0,0.3,1.0),
1384 "exec": compile_image
,
1388 static
.USER_EXIT
=False
1389 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
1390 wm
.modal_handler_add(_
)
1392 cxr_jobs_update_graph( static
.JOBINFO
)
1394 return {'RUNNING_MODAL'}
1396 print("Chain exiting...")
1397 static
.USER_EXIT
=True
1398 return {'RUNNING_MODAL'}
1401 # ------------------------------------------------------------------------------
1403 # Helper buttons for 3d toolbox view
1405 class CXR_VIEW3D( bpy
.types
.Panel
):
1406 bl_idname
= "VIEW3D_PT_convexer"
1407 bl_label
= "Convexer"
1408 bl_space_type
= 'VIEW_3D'
1409 bl_region_type
= 'UI'
1410 bl_category
= "Convexer"
1413 def poll(cls
, context
):
1414 return (context
.object is not None)
1416 def draw(_
, context
):
1420 row
.operator("convexer.preview")
1422 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
1424 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
1426 # Main scene properties interface, where all the settings go
1428 class CXR_INTERFACE(bpy
.types
.Panel
):
1430 bl_idname
="SCENE_PT_convexer"
1431 bl_space_type
='PROPERTIES'
1432 bl_region_type
='WINDOW'
1435 def draw(_
,context
):
1436 _
.layout
.operator("convexer.reload")
1437 _
.layout
.operator("convexer.dev_test")
1438 _
.layout
.operator("convexer.preview")
1440 settings
= context
.scene
.cxr_data
1442 _
.layout
.prop(settings
, "debug")
1443 _
.layout
.prop(settings
, "scale_factor")
1444 _
.layout
.prop(settings
, "lightmap_scale")
1445 _
.layout
.prop(settings
, "light_scale" )
1446 _
.layout
.prop(settings
, "image_quality" )
1448 box
= _
.layout
.box()
1450 box
.prop(settings
, "project_name")
1451 box
.prop(settings
, "subdir")
1453 box
= _
.layout
.box()
1454 box
.operator("convexer.detect_compilers")
1455 box
.prop(settings
, "exe_studiomdl")
1456 box
.prop(settings
, "exe_vbsp")
1457 box
.prop(settings
, "exe_vvis")
1458 box
.prop(settings
, "exe_vrad")
1460 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
1463 row
.operator("convexer.chain", text
=text
)
1466 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1467 bl_label
="VMT Properties"
1468 bl_idname
="SCENE_PT_convexer_vmt"
1469 bl_space_type
='PROPERTIES'
1470 bl_region_type
='WINDOW'
1471 bl_context
="material"
1473 def draw(_
,context
):
1474 active_object
= bpy
.context
.active_object
1475 if active_object
== None: return
1477 active_material
= active_object
.active_material
1478 if active_material
== None: return
1480 properties
= active_material
.cxr_data
1481 info
= material_info( active_material
)
1483 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1484 _
.layout
.prop( properties
, "shader" )
1487 _
.layout
.label(text
=F
"{xk}:={info[xk]}")
1489 def _mtex( name
, img
, uiParent
):
1492 box
= uiParent
.box()
1493 box
.label( text
=F
'{name} "{img.filepath}"' )
1495 if ((x
& (x
- 1)) == 0):
1498 closest_diff
= 10000000
1500 dist
= abs((1 << i
)-x
)
1501 if dist
< closest_diff
:
1506 return 1 << (closest
+1)
1508 return 1 << (closest
-1)
1513 row
.prop( img
.cxr_data
, "export_res" )
1514 row
.prop( img
.cxr_data
, "fmt" )
1517 row
.prop( img
.cxr_data
, "mipmap" )
1518 row
.prop( img
.cxr_data
, "lod" )
1519 row
.prop( img
.cxr_data
, "clamp" )
1521 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1522 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1524 def _mview( layer
, uiParent
):
1528 if isinstance(layer
[decl
],dict): # $property definition
1530 ptype
= pdef
['type']
1536 if ('shaders' in pdef
) and \
1537 (properties
.shader
not in pdef
['shaders']):
1540 if ptype
== 'intrinsic':
1541 if decl
not in info
:
1546 if isinstance(pdef
[ch
],dict):
1547 if ptype
== 'ui' or ptype
== 'intrinsic':
1549 elif getattr(properties
,decl
) == pdef
['default']:
1552 thisnode
= uiParent
.box()
1556 thisnode
.label( text
=decl
)
1557 elif ptype
== 'intrinsic':
1558 if isinstance(info
[decl
], bpy
.types
.Image
):
1559 _mtex( decl
, info
[decl
], thisnode
)
1561 # hidden intrinsic value.
1562 # Means its a float array or something not an image
1563 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
1565 thisnode
.prop(properties
,decl
)
1566 if expandview
: _mview(pdef
,thisnode
)
1568 _mview( cxr_shader_params
, _
.layout
)
1570 def cxr_entity_changeclass(_
,context
):
1571 active_object
= context
.active_object
1573 # Create ID properties
1575 classname
= active_object
.cxr_data
.classname
1577 if classname
in cxr_entities
:
1578 entdef
= cxr_entities
[classname
]
1580 kvs
= entdef
['keyvalues']
1581 if callable(kvs
): kvs
= kvs(active_object
)
1587 if callable(kv
) or not isinstance(kv
,dict): continue
1589 if key
not in active_object
:
1590 active_object
[key
] = kv
['default']
1591 id_prop
= active_object
.id_properties_ui(key
)
1592 id_prop
.update(default
=kv
['default'])
1594 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
1595 bl_label
="Entity Config"
1596 bl_idname
="SCENE_PT_convexer_entity"
1597 bl_space_type
='PROPERTIES'
1598 bl_region_type
='WINDOW'
1601 def draw(_
,context
):
1602 active_object
= bpy
.context
.active_object
1604 if active_object
== None: return
1606 default_context
= cxr_object_context( \
1607 bpy
.context
.scene
.cxr_data
.scale_factor
, 0.0 )
1609 ecn
= cxr_intrinsic_classname( active_object
)
1610 classname
= cxr_custom_class( active_object
)
1613 if active_object
.type == 'MESH':
1614 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
1615 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
1617 if classname
== 'NONE':
1620 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
1621 _
.layout
.enabled
=False
1624 kvs
= cxr_entity_keyvalues( active_object
, default_context
, classname
)
1628 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
1630 row
= _
.layout
.row()
1632 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
1634 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
1636 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
1637 bl_label
= "Source Settings"
1638 bl_idname
= "LIGHT_PT_cxr"
1639 bl_space_type
= 'PROPERTIES'
1640 bl_region_type
= 'WINDOW'
1643 def draw(self
, context
):
1644 layout
= self
.layout
1645 scene
= context
.scene
1647 active_object
= bpy
.context
.active_object
1648 if active_object
== None: return
1650 if active_object
.type == 'LIGHT' or \
1651 active_object
.type == 'LIGHT_PROBE':
1653 properties
= active_object
.data
.cxr_data
1655 if active_object
.type == 'LIGHT':
1656 layout
.prop( properties
, "realtime" )
1657 elif active_object
.type == 'LIGHT_PROBE':
1658 layout
.prop( properties
, "size" )
1661 # ------------------------------------------------------------------------------
1663 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
1664 export_res
: bpy
.props
.IntVectorProperty(
1666 description
="Texture Export Resolution",
1672 fmt
: bpy
.props
.EnumProperty(
1675 ('DXT1', "DXT1", "BC1 compressed", '', 0),
1676 ('DXT5', "DXT5", "BC3 compressed", '', 1),
1677 ('RGB', "RGB", "Uncompressed", '', 2),
1678 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
1680 description
="Image format",
1683 last_hash
: bpy
.props
.StringProperty( name
="" )
1684 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1686 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
1687 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
1688 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
1689 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
1691 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
1692 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
1694 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
1695 size
: bpy
.props
.EnumProperty(
1698 ('1',"1x1",'','',0),
1699 ('2',"2x2",'','',1),
1700 ('3',"4x4",'','',2),
1701 ('4',"8x8",'','',3),
1702 ('5',"16x16",'','',4),
1703 ('6',"32x32",'','',5),
1704 ('7',"64x64",'','',6),
1705 ('8',"128x128",'','',7),
1706 ('9',"256x256",'','',8)
1708 description
="Texture resolution",
1711 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
1712 entity
: bpy
.props
.BoolProperty(name
="")
1714 enum_pointents
= [('NONE',"None","")]
1715 enum_brushents
= [('NONE',"None","")]
1717 for classname
in cxr_entities
:
1718 entdef
= cxr_entities
[classname
]
1719 if 'allow' in entdef
:
1720 itm
= [(classname
, classname
, "")]
1721 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
1722 else: enum_brushents
+= itm
1724 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
1725 update
=cxr_entity_changeclass
, default
='NONE' )
1727 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
1728 update
=cxr_entity_changeclass
, default
='NONE' )
1730 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
1731 last_hash
: bpy
.props
.StringProperty( name
="" )
1732 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
1734 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
1735 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
1736 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
1738 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
1739 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
1740 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
1741 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
1742 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
1743 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
1744 opt_vrad
: bpy
.props
.StringProperty( name
="args" )
1746 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
1747 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
1748 default
=32.0,min=1.0)
1749 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
1750 default
=1.0,min=0.01)
1752 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
1753 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
1754 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
1756 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
1758 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
1759 default
=8, min=0, max=18 )
1762 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
1763 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
1764 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
1765 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
1766 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
1767 CXR_VIEW3D
, CXR_COMPILER_CHAIN
]
1769 vmt_param_dynamic_class
= None
1772 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
1775 bpy
.utils
.register_class(c
)
1777 # Build dynamic VMT properties class defined by cxr_shader_params
1778 annotations_dict
= {}
1780 def _dvmt_propogate(layer
):
1781 nonlocal annotations_dict
1784 if isinstance(layer
[decl
],dict): # $property definition
1788 if pdef
['type'] == 'bool':
1789 prop
= bpy
.props
.BoolProperty(\
1790 name
= pdef
['name'],\
1791 default
= pdef
['default'])
1793 elif pdef
['type'] == 'float':
1794 prop
= bpy
.props
.FloatProperty(\
1795 name
= pdef
['name'],\
1796 default
= pdef
['default'])
1798 elif pdef
['type'] == 'vector':
1799 if 'subtype' in pdef
:
1800 prop
= bpy
.props
.FloatVectorProperty(\
1801 name
= pdef
['name'],\
1802 subtype
= pdef
['subtype'],\
1803 default
= pdef
['default'],\
1804 size
= len(pdef
['default']))
1806 prop
= bpy
.props
.FloatVectorProperty(\
1807 name
= pdef
['name'],\
1808 default
= pdef
['default'],\
1809 size
= len(pdef
['default']))
1811 elif pdef
['type'] == 'string':
1812 prop
= bpy
.props
.StringProperty(\
1813 name
= pdef
['name'],\
1814 default
= pdef
['default'])
1816 elif pdef
['type'] == 'enum':
1817 prop
= bpy
.props
.EnumProperty(\
1818 name
= pdef
['name'],\
1819 items
= pdef
['items'],\
1820 default
= pdef
['default'])
1823 annotations_dict
[decl
] = prop
1825 # Recurse into sub-definitions
1826 _dvmt_propogate(pdef
)
1828 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
1831 cxr_shaders
[_
]["name"],\
1832 '') for _
in cxr_shaders
],\
1833 default
= next(iter(cxr_shaders
)))
1835 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
1838 _dvmt_propogate( cxr_shader_params
)
1839 vmt_param_dynamic_class
= type(
1841 (bpy
.types
.PropertyGroup
,),{
1842 "__annotations__": annotations_dict
1846 bpy
.utils
.register_class( vmt_param_dynamic_class
)
1849 bpy
.types
.Material
.cxr_data
= \
1850 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
1851 bpy
.types
.Image
.cxr_data
= \
1852 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
1853 bpy
.types
.Object
.cxr_data
= \
1854 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
1855 bpy
.types
.Collection
.cxr_data
= \
1856 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
1857 bpy
.types
.Light
.cxr_data
= \
1858 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
1859 bpy
.types
.LightProbe
.cxr_data
= \
1860 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
1861 bpy
.types
.Scene
.cxr_data
= \
1862 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
1864 # CXR Scene settings
1867 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
1868 cxr_draw
,(),'WINDOW','POST_VIEW')
1870 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
1871 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
1873 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
1874 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
1877 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
1879 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
1881 bpy
.utils
.unregister_class(c
)
1883 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
1884 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
1886 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
1887 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')