1 # Copyright (C) 2022 Harry Godden (hgn)
5 "author": "Harry Godden (hgn)",
12 "category":"Import/Export",
15 print( "Convexer reload" )
17 #from mathutils import *
18 import bpy
, gpu
, math
, os
, time
, mathutils
, blf
, subprocess
, shutil
, hashlib
20 from gpu_extras
.batch
import batch_for_shader
21 from bpy
.app
.handlers
import persistent
23 # GPU and viewport drawing
24 # ------------------------------------------------------------------------------
27 cxr_view_draw_handler
= None
28 cxr_ui_draw_handler
= None
37 cxr_view_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
38 cxr_ui_shader
= gpu
.types
.GPUShader("""
39 uniform mat4 ModelViewProjectionMatrix;
49 gl_Position = ModelViewProjectionMatrix * vec4(aPos.x*scale,aPos.y, 0.0, 1.0);
64 def cxr_ui(_
,context
):
65 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
67 w
= gpu
.state
.viewport_get()[2]
69 cxr_ui_shader
.uniform_float( "scale", w
)
71 if cxr_jobs_batch
!= None:
72 gpu
.state
.blend_set('ALPHA')
73 cxr_jobs_batch
.draw(cxr_ui_shader
)
75 blf
.position(0,2,50,0)
77 blf
.color(0,1.0,1.0,1.0,1.0)
78 blf
.draw(0,"Compiling")
80 for ji
in cxr_jobs_inf
:
81 blf
.position(0,ji
[0]*w
,35,0)
87 for ln
in reversed(CXR_COMPILER_CHAIN
.LOG
[-25:]):
88 blf
.position(0,2,py
,0)
92 #if CXR_PREVIEW_OPERATOR.LASTERR != None:
93 # blf.position(0,2,80,0)
95 # blf.color(0,1.0,0.2,0.2,0.9)
96 # blf.draw(0,"Invalid geometry")
98 # Something is off with TIMER,
99 # this forces the viewport to redraw before we can continue with our
102 CXR_COMPILER_CHAIN
.WAIT_REDRAW
= False
105 global cxr_view_shader
, cxr_view_mesh
, cxr_view_lines
107 cxr_view_shader
.bind()
109 gpu
.state
.depth_mask_set(False)
110 gpu
.state
.line_width_set(1.5)
111 gpu
.state
.face_culling_set('BACK')
112 gpu
.state
.depth_test_set('NONE')
113 gpu
.state
.blend_set('ALPHA')
115 if cxr_view_lines
!= None:
116 cxr_view_lines
.draw( cxr_view_shader
)
118 gpu
.state
.depth_test_set('LESS_EQUAL')
119 gpu
.state
.blend_set('ADDITIVE')
120 if cxr_view_mesh
!= None:
121 cxr_view_mesh
.draw( cxr_view_shader
)
123 def cxr_jobs_update_graph(jobs
):
124 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
134 total_width
+= sys
['w']
143 colour
= sys
['colour']
144 colourwait
= (colour
[0],colour
[1],colour
[2],0.4)
145 colourrun
= (colour
[0]*1.5,colour
[1]*1.5,colour
[2]*1.5,0.5)
146 colourdone
= (colour
[0],colour
[1],colour
[2],1.0)
149 sfsub
= (1.0/(len(jobs
)))*w
153 if j
== None: colour
= colourdone
154 else: colour
= colourwait
156 px
= (cur
+ (i
)*sfsub
) * sf
157 px1
= (cur
+ (i
+1.0)*sfsub
) * sf
160 verts
+= [(px
,0), (px
, h
), (px1
, 0.0), (px1
,h
)]
161 colours
+= [colour
,colour
,colour
,colour
]
162 indices
+= [(ci
+0,ci
+2,ci
+3),(ci
+0,ci
+3,ci
+1)]
165 cxr_jobs_inf
+= [((sf
*cur
), sys
['title'])]
168 cxr_jobs_batch
= batch_for_shader(
169 cxr_ui_shader
, 'TRIS',
170 { "aPos": verts
, "aColour": colours
},
174 # view_layer.update() doesnt seem to work,
175 # tag_redraw() seems to have broken
176 # therefore, change a property
178 ob
= bpy
.context
.scene
.objects
[0]
179 ob
.hide_render
= ob
.hide_render
181 # the 'real' way to refresh the scene
182 for area
in bpy
.context
.window
.screen
.areas
:
183 if area
.type == 'view_3d':
187 # ------------------------------------------------------------------------------
189 # dlclose for reloading modules manually
191 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
192 libc_dlclose
.argtypes
= [c_void_p
]
194 # wrapper for ctypes binding
196 def __init__(_
,name
,argtypes
,restype
):
198 _
.argtypes
= argtypes
203 _
.call
= getattr(so
,_
.name
)
204 _
.call
.argtypes
= _
.argtypes
206 if _
.restype
!= None:
207 _
.call
.restype
= _
.restype
210 # ------------------------------------------------------------------------------
214 # Structure definitions
216 class cxr_edge(Structure
):
217 _fields_
= [("i0",c_int32
),
219 ("freestyle",c_int32
),
222 class cxr_static_loop(Structure
):
223 _fields_
= [("index",c_int32
),
224 ("edge_index",c_int32
),
227 class cxr_polygon(Structure
):
228 _fields_
= [("loop_start",c_int32
),
229 ("loop_total",c_int32
),
230 ("normal",c_double
* 3),
231 ("center",c_double
* 3),
232 ("material_id",c_int32
)]
234 class cxr_material(Structure
):
235 _fields_
= [("res",c_int32
* 2),
238 class cxr_static_mesh(Structure
):
239 _fields_
= [("vertices",POINTER(c_double
* 3)),
240 ("edges",POINTER(cxr_edge
)),
241 ("loops",POINTER(cxr_static_loop
)),
242 ("polys",POINTER(cxr_polygon
)),
243 ("materials",POINTER(cxr_material
)),
245 ("poly_count",c_int32
),
246 ("vertex_count",c_int32
),
247 ("edge_count",c_int32
),
248 ("loop_count",c_int32
),
249 ("material_count",c_int32
)]
251 class cxr_tri_mesh(Structure
):
252 _fields_
= [("vertices",POINTER(c_double
*3)),
253 ("colours",POINTER(c_double
*4)),
254 ("indices",POINTER(c_int32
)),
255 ("indices_count",c_int32
),
256 ("vertex_count",c_int32
)]
258 class cxr_vmf_context(Structure
):
259 _fields_
= [("mapversion",c_int32
),
260 ("skyname",c_char_p
),
261 ("detailvbsp",c_char_p
),
262 ("detailmaterial",c_char_p
),
264 ("offset",c_double
*3),
265 ("lightmap_scale",c_int32
),
266 ("brush_count",c_int32
),
267 ("entity_count",c_int32
),
268 ("face_count",c_int32
)]
270 # Convert blenders mesh format into CXR's static format (they are very similar)
272 def mesh_cxr_format(obj
):
275 if bpy
.context
.active_object
!= None:
276 orig_state
= obj
.mode
277 if orig_state
!= 'OBJECT':
278 bpy
.ops
.object.mode_set(mode
='OBJECT')
280 dgraph
= bpy
.context
.evaluated_depsgraph_get()
281 data
= obj
.evaluated_get(dgraph
).data
283 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
285 mesh
= cxr_static_mesh()
287 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
288 for i
, vert
in enumerate(data
.vertices
):
289 v
= obj
.matrix_world
@ vert
.co
290 vertex_data
[i
][0] = c_double(v
[0])
291 vertex_data
[i
][1] = c_double(v
[1])
292 vertex_data
[i
][2] = c_double(v
[2])
294 loop_data
= (cxr_static_loop
*len(data
.loops
))()
295 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
297 for i
, poly
in enumerate(data
.polygons
):
298 loop_start
= poly
.loop_start
299 loop_end
= poly
.loop_start
+ poly
.loop_total
300 for loop_index
in range(loop_start
, loop_end
):
301 loop
= data
.loops
[loop_index
]
302 loop_data
[loop_index
].index
= loop
.vertex_index
303 loop_data
[loop_index
].edge_index
= loop
.edge_index
306 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
307 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
308 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
310 loop_data
[loop_index
].uv
[0] = c_double(0.0)
311 loop_data
[loop_index
].uv
[1] = c_double(0.0)
312 center
= obj
.matrix_world
@ poly
.center
313 normal
= mtx_rot
@ poly
.normal
315 polygon_data
[i
].loop_start
= poly
.loop_start
316 polygon_data
[i
].loop_total
= poly
.loop_total
317 polygon_data
[i
].normal
[0] = normal
[0]
318 polygon_data
[i
].normal
[1] = normal
[1]
319 polygon_data
[i
].normal
[2] = normal
[2]
320 polygon_data
[i
].center
[0] = center
[0]
321 polygon_data
[i
].center
[1] = center
[1]
322 polygon_data
[i
].center
[2] = center
[2]
323 polygon_data
[i
].material_id
= poly
.material_index
325 edge_data
= (cxr_edge
*len(data
.edges
))()
327 for i
, edge
in enumerate(data
.edges
):
328 edge_data
[i
].i0
= edge
.vertices
[0]
329 edge_data
[i
].i1
= edge
.vertices
[1]
330 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
331 edge_data
[i
].sharp
= edge
.use_edge_sharp
333 material_data
= (cxr_material
*len(obj
.material_slots
))()
335 for i
, ms
in enumerate(obj
.material_slots
):
336 inf
= material_info(ms
.material
)
337 material_data
[i
].res
[0] = inf
['res'][0]
338 material_data
[i
].res
[1] = inf
['res'][1]
339 material_data
[i
].name
= inf
['name'].encode('utf-8')
341 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
342 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
343 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
344 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
345 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
347 mesh
.poly_count
= len(data
.polygons
)
348 mesh
.vertex_count
= len(data
.vertices
)
349 mesh
.edge_count
= len(data
.edges
)
350 mesh
.loop_count
= len(data
.loops
)
351 mesh
.material_count
= len(obj
.material_slots
)
353 if orig_state
!= None:
354 bpy
.ops
.object.mode_set(mode
=orig_state
)
358 # Callback ctypes indirection things.. not really sure.
359 c_libcxr_log_callback
= None
360 c_libcxr_line_callback
= None
363 # -------------------------------------------------------------
364 libcxr_decompose
= extern( "cxr_decompose",
365 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
368 libcxr_free_world
= extern( "cxr_free_world",
372 libcxr_write_test_data
= extern( "cxr_write_test_data",
373 [POINTER(cxr_static_mesh
)],
376 libcxr_world_preview
= extern( "cxr_world_preview",
378 POINTER(cxr_tri_mesh
)
380 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
384 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
385 [POINTER(cxr_vmf_context
), c_void_p
],
388 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
389 [POINTER(cxr_vmf_context
), c_void_p
],
392 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
393 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
396 libcxr_end_vmf
= extern( "cxr_end_vmf",
397 [POINTER(cxr_vmf_context
),c_void_p
],
401 # VDF + with open wrapper
402 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
403 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
404 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
405 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
406 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
407 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
409 class vdf_structure():
410 def __init__(_
,path
):
413 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
415 print( F
"Could not open file {_.path}" )
418 def __exit__(_
,type,value
,traceback
):
420 libcxr_vdf_close
.call(_
.fp
)
422 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
424 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
426 libcxr_vdf_edon
.call(_
.fp
)
428 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
431 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
433 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
434 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
435 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
436 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
437 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
438 libcxr_world_preview
, libcxr_free_tri_mesh
]
442 def libcxr_log_callback(logStr
):
443 print( F
"{logStr.decode('utf-8')}",end
='' )
445 cxr_line_positions
= None
446 cxr_line_colours
= None
448 def cxr_reset_lines():
449 global cxr_line_positions
, cxr_line_colours
451 cxr_line_positions
= []
452 cxr_line_colours
= []
454 def cxr_batch_lines():
455 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
457 cxr_view_lines
= batch_for_shader(\
458 cxr_view_shader
, 'LINES',\
459 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
461 def libcxr_line_callback( p0
,p1
,colour
):
462 global cxr_line_colours
, cxr_line_positions
464 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
465 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
466 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
467 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
470 # ------------------------------------------------------------------------------
475 NBVTF_IMAGE_FORMAT_ABGR8888
= 1
476 NBVTF_IMAGE_FORMAT_BGR888
= 3
477 NBVTF_IMAGE_FORMAT_DXT1
= 13
478 NBVTF_IMAGE_FORMAT_DXT5
= 15
479 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
480 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
481 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
482 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
483 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
485 libnbvtf_convert
= extern( "nbvtf_convert", \
486 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
489 libnbvtf_init
= extern( "nbvtf_init", [], None )
490 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
493 # --------------------------
496 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
498 # Unload libraries if existing
499 def _reload( lib
, path
):
501 _handle
= lib
._handle
502 for i
in range(10): libc_dlclose( _handle
)
505 return cdll
.LoadLibrary( F
'{os.path.dirname(__file__)}/{path}.so' )
507 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
508 libcxr
= _reload( libcxr
, "libcxr" )
510 for fd
in libnbvtf_funcs
:
511 fd
.loadfrom( libnbvtf
)
514 for fd
in libcxr_funcs
:
515 fd
.loadfrom( libcxr
)
518 global c_libcxr_log_callback
, c_libcxr_line_callback
520 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
521 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
523 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
524 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
525 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
527 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
528 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
530 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
531 print( F
"libcxr build time: {build_time.value}" )
536 # ------------------------------------------------------------------------------
538 # Standard entity functions, think of like base.fgd
540 def cxr_get_origin(context
):
541 return context
['object'].location
* context
['transform']['scale'] + \
542 mathutils
.Vector(context
['transform']['offset'])
544 def cxr_get_angles(context
):
545 obj
= context
['object']
546 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
553 def cxr_baseclass(classes
, other
):
556 base
.update(x
.copy())
559 # EEVEE Light component converter -> Source 1
561 def ent_lights(context
):
562 obj
= context
['object']
563 kvs
= cxr_baseclass([ent_origin
],\
565 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
566 "_lightHDR": '-1 -1 -1 1',
570 light_base
= [(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
571 [obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
]
573 if obj
.data
.type == 'SPOT' or obj
.data
.type == 'SUN':
574 # Blenders directional lights are -z forward
575 # Source is +x, however, it seems to use a completely different system.
576 # Since we dont care about roll for spotlights, we just take the
577 # pitch and yaw via trig
579 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
580 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
581 dir_pitch
= math
.asin(fwd
[2]) * 57.295779513
582 dir_yaw
= math
.atan2(fwd
[1],fwd
[0]) * 57.295779513
584 if obj
.data
.type == 'SPOT':
585 kvs
['_light'] = [ int(x
) for x
in light_base
]
586 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
587 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
589 kvs
['pitch'] = dir_pitch
590 kvs
['angles'] = [ 0, dir_yaw
, 0 ]
591 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
594 # Blender's default has a much more 'nice'
596 kvs
['_linear_attn'] = 1.0
598 elif obj
.data
.type == 'POINT':
599 kvs
['_light'] = [ int(x
) for x
in light_base
]
600 kvs
['_quadratic_attn'] = 1.0
601 kvs
['_linear_attn'] = 0.0
603 elif obj
.data
.type == 'SUN':
604 light_base
[3] *= 300.0 * 5
605 kvs
['_light'] = [ int(x
) for x
in light_base
]
607 ambient
= bpy
.context
.scene
.world
.color
608 kvs
['_ambient'] = [int(pow(ambient
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
610 kvs
['_ambientHDR'] = [-1,-1,-1,1]
611 kvs
['_AmbientScaleHDR'] = 1
612 kvs
['pitch'] = dir_pitch
613 kvs
['angles'] = [ dir_pitch
, dir_yaw
, 0.0 ]
614 kvs
['SunSpreadAngle'] = 0
618 def ent_prop(context
):
619 if isinstance( context
['object'], bpy
.types
.Collection
):
621 target
= context
['object']
622 pos
= mathutils
.Vector(context
['origin'])
623 pos
+= mathutils
.Vector(context
['transform']['offset'])
625 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
626 kvs
['angles'] = [0,180,0]
628 kvs
= cxr_baseclass([ent_origin
],{})
629 target
= context
['object'].instance_collection
631 obj
= context
['object']
632 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
635 angle
[1] = euler
[2] + 180.0 # Dunno...
638 kvs
['angles'] = angle
641 kvs
['enablelightbounce'] = 1
642 kvs
['disableshadows'] = 0
643 kvs
['fademindist'] = -1
645 kvs
['model'] = F
"{asset_path('models',target)}.mdl".lower()
646 kvs
['renderamt'] = 255
647 kvs
['rendercolor'] = [255, 255, 255]
650 kvs
['uniformscale'] = 1.0
654 def ent_sky_camera(context
):
655 settings
= bpy
.context
.scene
.cxr_data
656 scale
= settings
.scale_factor
/ settings
.skybox_scale_factor
659 "origin": [_
for _
in context
['transform']['offset']],
660 "angles": [ 0, 0, 0 ],
661 "fogcolor": [255, 255, 255],
662 "fogcolor2": [255, 255, 255],
667 "HDRColorScale": 1.0,
672 def ent_cubemap(context
):
673 obj
= context
['object']
674 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
676 ent_origin
= { "origin": cxr_get_origin
}
677 ent_angles
= { "angles": cxr_get_angles
}
678 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
680 #include the user config
681 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
683 # Blender state callbacks
684 # ------------------------------------------------------------------------------
687 def cxr_on_load(dummy
):
688 global cxr_view_lines
, cxr_view_mesh
690 cxr_view_lines
= None
694 def cxr_dgraph_update(scene
,dgraph
):
696 print( F
"Hallo {time.time()}" )
698 # Convexer compilation functions
699 # ------------------------------------------------------------------------------
701 # Asset path management
703 def asset_uid(asset
):
704 if isinstance(asset
,str):
707 # Create a unique ID string
709 v
= asset
.cxr_data
.asset_id
718 dig
.append( int( v
% len(base
) ) )
724 if bpy
.context
.scene
.cxr_data
.include_names
:
725 name
+= asset
.name
.replace('.','_')
729 # -> <project_name>/<asset_name>
730 def asset_name(asset
):
731 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
733 # -> <subdir>/<project_name>/<asset_name>
734 def asset_path(subdir
, asset
):
735 return F
"{subdir}/{asset_name(asset_uid(asset))}"
737 # -> <csgo>/<subdir>/<project_name>/<asset_name>
738 def asset_full_path(sdir
,asset
):
739 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
740 F
"{asset_path(sdir,asset_uid(asset))}"
742 # Entity functions / infos
743 # ------------------------
745 def cxr_intrinsic_classname(obj
):
746 if obj
.type == 'LIGHT':
748 'SPOT': "light_spot",
750 'SUN': "light_environment" }[ obj
.data
.type ]
752 elif obj
.type == 'LIGHT_PROBE':
754 elif obj
.type == 'EMPTY':
760 def cxr_custom_class(obj
):
761 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
762 else: custom_class
= obj
.cxr_data
.classname
766 def cxr_classname(obj
):
767 intr
= cxr_intrinsic_classname(obj
)
768 if intr
!= None: return intr
770 custom_class
= cxr_custom_class(obj
)
771 if custom_class
!= 'NONE':
777 # intinsic: (k, False, value)
778 # property: (k, True, value or default)
782 def cxr_entity_keyvalues(context
):
783 classname
= context
['classname']
784 obj
= context
['object']
785 if classname
not in cxr_entities
: return None
789 entdef
= cxr_entities
[classname
]
790 kvs
= entdef
['keyvalues']
792 if callable(kvs
): kvs
= kvs(context
)
799 if isinstance(kv
,dict):
801 value
= obj
[ F
"cxrkv_{k}" ]
806 if isinstance(value
,mathutils
.Vector
):
807 value
= [_
for _
in value
]
809 result
+= [(k
, isprop
, value
)]
813 # Extract material information from shader graph data
815 def material_info(mat
):
817 info
['res'] = (512,512)
818 info
['name'] = 'tools/toolsnodraw'
820 if mat
== None or mat
.use_nodes
== False:
824 if mat
.cxr_data
.shader
== 'Builtin':
825 info
['name'] = mat
.name
829 info
['name'] = asset_name(mat
)
831 # Using the cxr_graph_mapping as a reference, go through the shader
832 # graph and gather all $props from it.
834 def _graph_read( node_def
, node
=None, depth
=0 ):
838 def _variant_apply( val
):
841 if isinstance( val
, str ):
844 for shader_variant
in val
:
845 if shader_variant
[0] == mat
.cxr_data
.shader
:
846 return shader_variant
[1]
850 _graph_read
.extracted
= []
852 for node_idname
in node_def
:
853 for n
in mat
.node_tree
.nodes
:
854 if n
.bl_idname
== node_idname
:
855 node_def
= node_def
[node_idname
]
859 for link
in node_def
:
860 if isinstance( node_def
[link
], dict ):
861 inputt
= node
.inputs
[link
]
862 inputt_def
= node_def
[link
]
866 # look for definitions for the connected node type
867 con
= inputt
.links
[0].from_node
869 for node_idname
in inputt_def
:
870 if con
.bl_idname
== node_idname
:
871 con_def
= inputt_def
[ node_idname
]
872 _graph_read( con_def
, con
, depth
+1 )
874 # No definition found! :(
875 # TODO: Make a warning for this?
878 if "default" in inputt_def
:
879 prop
= _variant_apply( inputt_def
['default'] )
880 info
[prop
] = inputt
.default_value
882 prop
= _variant_apply( node_def
[link
] )
883 info
[prop
] = getattr(node
,link
)
885 _graph_read(cxr_graph_mapping
)
887 if "$basetexture" in info
:
888 export_res
= info
['$basetexture'].cxr_data
.export_res
889 info
['res'] = (export_res
[0], export_res
[1])
893 def vec3_min( a
, b
):
894 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
895 def vec3_max( a
, b
):
896 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
898 def cxr_collection_center(collection
, transform
):
900 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
901 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
903 for obj
in collection
.objects
:
904 if obj
.type == 'MESH':
905 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
907 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
908 bounds_min
= vec3_min( bounds_min
, corner
)
909 bounds_max
= vec3_max( bounds_max
, corner
)
911 center
= (bounds_min
+ bounds_max
) / 2.0
913 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
914 origin
*= transform
['scale']
918 # Prepares Scene into dictionary format
920 def cxr_scene_collect():
921 context
= bpy
.context
923 # Make sure all of our asset types have a unique ID
924 def _uid_prepare(objtype
):
930 if vs
.asset_id
in used_ids
:
933 id_max
= max(id_max
,vs
.asset_id
)
934 used_ids
+=[vs
.asset_id
]
935 for vs
in to_generate
:
938 _uid_prepare(bpy
.data
.materials
)
939 _uid_prepare(bpy
.data
.images
)
940 _uid_prepare(bpy
.data
.collections
)
943 "entities": [], # Everything with a classname
944 "geo": [], # All meshes without a classname
945 "heros": [] # Collections prefixed with mdl_
948 def _collect(collection
,transform
):
951 if collection
.name
.startswith('.'): return
952 if collection
.hide_render
: return
954 if collection
.name
.startswith('mdl_'):
955 sceneinfo
['entities'] += [{
956 "object": collection
,
957 "classname": "prop_static",
958 "transform": transform
,
959 "origin": cxr_collection_center( collection
, transform
)
962 sceneinfo
['heros'] += [{
963 "collection": collection
,
964 "transform": transform
,
965 "origin": cxr_collection_center( collection
, transform
)
969 for obj
in collection
.objects
:
970 if obj
.hide_get(): continue
972 classname
= cxr_classname( obj
)
974 if classname
!= None:
975 sceneinfo
['entities'] += [{
977 "classname": classname
,
978 "transform": transform
980 elif obj
.type == 'MESH':
981 sceneinfo
['geo'] += [{
983 "transform": transform
986 for c
in collection
.children
:
987 _collect( c
, transform
)
990 "scale": context
.scene
.cxr_data
.scale_factor
,
995 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
996 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
999 if 'main' in bpy
.data
.collections
:
1000 _collect( bpy
.data
.collections
['main'], transform_main
)
1002 if 'skybox' in bpy
.data
.collections
:
1003 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1005 sceneinfo
['entities'] += [{
1007 "transform": transform_sky
,
1008 "classname": "sky_camera"
1013 # Write VMF out to file (JOB HANDLER)
1015 def cxr_export_vmf(sceneinfo
, output_vmf
):
1018 with
vdf_structure(output_vmf
) as m
:
1019 print( F
"Write: {output_vmf}" )
1021 vmfinfo
= cxr_vmf_context()
1022 vmfinfo
.mapversion
= 4
1024 #TODO: These need to be in options...
1025 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1026 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1027 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1028 vmfinfo
.lightmap_scale
= 12
1030 vmfinfo
.brush_count
= 0
1031 vmfinfo
.entity_count
= 0
1032 vmfinfo
.face_count
= 0
1034 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1036 def _buildsolid( cmd
):
1039 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1041 baked
= mesh_cxr_format( cmd
['object'] )
1042 world
= libcxr_decompose
.call( baked
, None )
1047 vmfinfo
.scale
= cmd
['transform']['scale']
1049 offset
= cmd
['transform']['offset']
1050 vmfinfo
.offset
[0] = offset
[0]
1051 vmfinfo
.offset
[1] = offset
[1]
1052 vmfinfo
.offset
[2] = offset
[2]
1054 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1055 libcxr_free_world
.call( world
)
1060 for brush
in sceneinfo
['geo']:
1061 if not _buildsolid( brush
):
1066 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1069 for ent
in sceneinfo
['entities']:
1071 ctx
= ent
['transform']
1072 cls
= ent
['classname']
1075 m
.kv( 'classname', cls
)
1077 kvs
= cxr_entity_keyvalues( ent
)
1080 if isinstance(kv
[2], list):
1081 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1082 else: m
.kv( kv
[0], str(kv
[2]) )
1086 elif not isinstance( obj
, bpy
.types
.Collection
):
1087 if obj
.type == 'MESH':
1088 if not _buildsolid( ent
):
1098 # COmpile image using NBVTF and hash it (JOB HANDLER)
1100 def compile_image(img
):
1104 name
= asset_name(img
)
1105 src_path
= bpy
.path
.abspath(img
.filepath
)
1107 dims
= img
.cxr_data
.export_res
1109 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1110 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1111 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1112 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1113 }[ img
.cxr_data
.fmt
]
1115 mipmap
= img
.cxr_data
.mipmap
1116 lod
= img
.cxr_data
.lod
1117 clamp
= img
.cxr_data
.clamp
1118 flags
= img
.cxr_data
.flags
1120 q
=bpy
.context
.scene
.cxr_data
.image_quality
1122 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1123 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1124 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1126 if img
.cxr_data
.last_hash
!= comphash
:
1127 print( F
"Texture update: {img.filepath}" )
1129 src
= src_path
.encode('utf-8')
1130 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1134 # texture setting flags
1135 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1137 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1138 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1140 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1141 img
.cxr_data
.last_hash
= comphash
1146 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1149 def compile_material(mat
):
1150 info
= material_info(mat
)
1151 properties
= mat
.cxr_data
1153 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1154 if properties
.shader
== 'Builtin':
1159 # Walk the property tree
1160 def _mlayer( layer
):
1161 nonlocal properties
, props
1164 if isinstance(layer
[decl
],dict): # $property definition
1166 ptype
= pdef
['type']
1172 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1175 # Group expansion (does it have subdefinitions?)
1177 if isinstance(pdef
[ch
],dict):
1186 if ptype
== 'intrinsic':
1190 prop
= getattr(properties
,decl
)
1191 default
= pdef
['default']
1193 if not isinstance(prop
,str) and \
1194 not isinstance(prop
,bpy
.types
.Image
) and \
1195 hasattr(prop
,'__getitem__'):
1196 prop
= tuple([p
for p
in prop
])
1200 props
+= [(decl
,pdef
,prop
)]
1205 if expandview
: _mlayer(pdef
)
1207 _mlayer( cxr_shader_params
)
1210 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1211 vmt
.node( properties
.shader
)
1212 vmt
.put( "// Convexer export\n" )
1221 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1224 if isinstance(prop
,bpy
.types
.Image
):
1225 vmt
.kv( decl
, asset_name(prop
))
1226 elif isinstance(prop
,bool):
1227 vmt
.kv( decl
, '1' if prop
else '0' )
1228 elif isinstance(prop
,str):
1229 vmt
.kv( decl
, prop
)
1230 elif isinstance(prop
,float) or isinstance(prop
,int):
1231 vmt
.kv( decl
, _numeric(prop
) )
1232 elif isinstance(prop
,tuple):
1233 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1235 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1240 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1241 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1243 # Compute hash value
1244 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1246 #for obj in mdl.objects:
1247 # if obj.type != 'MESH':
1250 # ev = obj.evaluated_get(dgraph).data
1251 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1252 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1254 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1255 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1257 # if ev.uv_layers.active != None:
1258 # uv_layer = ev.uv_layers.active.data
1259 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1263 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1264 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1265 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1266 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1267 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1268 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1270 #if chash != mdl.cxr_data.last_hash:
1271 # mdl.cxr_data.last_hash = chash
1272 # print( F"Compile: {mdl.name}" )
1276 bpy
.ops
.object.select_all(action
='DESELECT')
1279 def _get_layer(col
,name
):
1280 for c
in col
.children
:
1283 sub
= _get_layer(c
,name
)
1287 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1289 prev_state
= layer
.hide_viewport
1290 layer
.hide_viewport
=False
1292 # Collect materials to be compiled, and temp rename for export
1296 for obj
in mdl
.objects
:
1297 if obj
.name
== F
"{mdl.name}_phy":
1301 obj
.select_set(state
=True)
1302 for ms
in obj
.material_slots
:
1303 if ms
.material
!= None:
1304 if ms
.material
not in mat_dict
:
1305 mat_dict
[ms
.material
] = ms
.material
.name
1306 ms
.material
.name
= asset_uid(ms
.material
)
1307 ms
.material
.use_nodes
= False
1310 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1311 check_existing
=False,
1313 apply_unit_scale
=False,
1314 bake_space_transform
=False
1317 bpy
.ops
.object.select_all(action
='DESELECT')
1320 vphys
.select_set(state
=True)
1321 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1322 check_existing
=False,
1324 apply_unit_scale
=False,
1325 bake_space_transform
=False
1327 bpy
.ops
.object.select_all(action
='DESELECT')
1329 # Fix material names back to original
1330 for mat
in mat_dict
:
1331 mat
.name
= mat_dict
[mat
]
1332 mat
.use_nodes
= True
1334 layer
.hide_viewport
=prev_state
1337 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1338 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1339 #o.write(F'$scale .32\n')
1340 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1341 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1342 o
.write(F
'$staticprop\n')
1343 o
.write(F
'$origin {origin[0]} {origin[1]} {origin[2]}\n')
1346 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1348 o
.write(" $concave\n")
1351 o
.write(F
'$cdmaterials {project_name}\n')
1352 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1356 # Copy bsp file (and also lightpatch it)
1358 def cxr_patchmap( src
, dst
):
1359 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1360 shutil
.copyfile( src
, dst
)
1363 # Convexer operators
1364 # ------------------------------------------------------------------------------
1366 # Force reload of shared libraries
1368 class CXR_RELOAD(bpy
.types
.Operator
):
1369 bl_idname
="convexer.reload"
1371 def execute(_
,context
):
1375 # Used for exporting data to use with ASAN builds
1377 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1378 bl_idname
="convexer.dev_test"
1379 bl_label
="Export development data"
1381 def execute(_
,context
):
1382 # Prepare input data
1383 mesh_src
= mesh_cxr_format(context
.active_object
)
1384 libcxr_write_test_data
.call( pointer(mesh_src
) )
1387 # UI: Preview how the brushes will looks in 3D view
1389 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1390 bl_idname
="convexer.preview"
1391 bl_label
="Preview Brushes"
1396 def execute(_
,context
):
1399 def modal(_
,context
,event
):
1400 global cxr_view_mesh
1401 static
= _
.__class
__
1403 if event
.type == 'ESC':
1406 cxr_view_mesh
= None
1407 static
.RUNNING
= False
1412 return {'PASS_THROUGH'}
1414 def invoke(_
,context
,event
):
1415 global cxr_view_shader
, cxr_view_mesh
1416 static
= _
.__class
__
1417 static
.LASTERR
= None
1421 mesh_src
= mesh_cxr_format(context
.active_object
)
1424 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1427 cxr_view_mesh
= None
1431 static
.LASTERR
= ["There is no error", \
1437 "Non-Convex Polygon",\
1442 return {'CANCELLED'}
1444 context
.window_manager
.modal_handler_add(_
)
1445 return {'RUNNING_MODAL'}
1447 # Generate preview using cxr
1449 ptrpreview
= libcxr_world_preview
.call( world
)
1450 preview
= ptrpreview
[0]
1452 vertices
= preview
.vertices
[:preview
.vertex_count
]
1453 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1455 colours
= preview
.colours
[:preview
.vertex_count
]
1456 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1458 indices
= preview
.indices
[:preview
.indices_count
]
1459 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1460 for i
in range(int(preview
.indices_count
/3)) ]
1462 cxr_view_mesh
= batch_for_shader(
1463 cxr_view_shader
, 'TRIS',
1464 { "pos": vertices
, "color": colours
},
1468 libcxr_free_tri_mesh
.call( ptrpreview
)
1469 libcxr_free_world
.call( world
)
1473 # Allow user to spam the operator
1475 return {'CANCELLED'}
1477 if not static
.RUNNING
:
1478 static
.RUNNING
= True
1479 context
.window_manager
.modal_handler_add(_
)
1480 return {'RUNNING_MODAL'}
1482 # Search for VMF compiler executables in subdirectory
1484 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1485 bl_idname
="convexer.detect_compilers"
1486 bl_label
="Find compilers"
1488 def execute(self
,context
):
1489 scene
= context
.scene
1490 settings
= scene
.cxr_data
1491 subdir
= settings
.subdir
1493 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1494 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1495 if os
.path
.exists(searchpath
):
1496 settings
[F
'exe_{exename}'] = searchpath
1500 def cxr_compiler_path( compiler
):
1501 settings
= bpy
.context
.scene
.cxr_data
1502 subdir
= settings
.subdir
1503 path
= os
.path
.normpath(F
'{subdir}/../bin/{compiler}.exe')
1505 if os
.path
.exists( path
): return path
1508 def cxr_winepath( path
):
1509 return 'z:'+path
.replace('/','\\')
1511 # Main compile function
1513 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1514 bl_idname
="convexer.chain"
1515 bl_label
="Compile Chain"
1530 def cancel(_
,context
):
1531 global cxr_jobs_batch
1532 static
= _
.__class
__
1533 wm
= context
.window_manager
1535 if static
.SUBPROC
!= None:
1536 static
.SUBPROC
.terminate()
1537 static
.SUBPROC
= None
1539 if static
.TIMER
!= None:
1540 wm
.event_timer_remove( static
.TIMER
)
1545 cxr_jobs_batch
= None
1549 def modal(_
,context
,ev
):
1550 static
= _
.__class
__
1552 if ev
.type == 'TIMER':
1553 global cxr_jobs_batch
1555 if static
.WAIT_REDRAW
:
1557 return {'PASS_THROUGH'}
1558 static
.WAIT_REDRAW
= True
1560 if static
.USER_EXIT
:
1561 print( "Chain USER_EXIT" )
1562 return _
.cancel(context
)
1564 if static
.SUBPROC
!= None:
1565 # Deal with async modes
1566 status
= static
.SUBPROC
.poll()
1569 # Cannot redirect STDOUT through here without causing
1570 # undefined behaviour due to the Blender Python specification.
1572 # Have to write it out to a file and read it back in.
1574 with
open("/tmp/convexer_compile_log.txt","r") as log
:
1575 static
.LOG
= log
.readlines()
1576 return {'PASS_THROUGH'}
1578 #for l in static.SUBPROC.stdout:
1579 # print( F'-> {l.decode("utf-8")}',end='' )
1580 static
.SUBPROC
= None
1583 print(F
'Compiler () error: {status}')
1584 return _
.cancel(context
)
1586 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1587 cxr_jobs_update_graph( static
.JOBINFO
)
1589 return {'PASS_THROUGH'}
1591 # Compile syncronous thing
1592 for sys
in static
.JOBINFO
:
1593 for i
,target
in enumerate(sys
['jobs']):
1596 if callable(sys
['exec']):
1597 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1599 if not sys
['exec'](*target
):
1600 print( "Job failed" )
1601 return _
.cancel(context
)
1603 sys
['jobs'][i
] = None
1606 # Run external executable (wine)
1607 static
.SUBPROC
= subprocess
.Popen( target
,
1608 stdout
=static
.FILE
,\
1609 stderr
=subprocess
.PIPE
,\
1614 cxr_jobs_update_graph( static
.JOBINFO
)
1616 return {'PASS_THROUGH'}
1619 print( "All jobs completed!" )
1620 cxr_jobs_batch
= None
1623 return _
.cancel(context
)
1625 return {'PASS_THROUGH'}
1627 def invoke(_
,context
,event
):
1628 static
= _
.__class
__
1629 wm
= context
.window_manager
1631 if static
.TIMER
!= None:
1632 print("Chain exiting...")
1633 static
.USER_EXIT
=True
1634 return {'RUNNING_MODAL'}
1636 print("Launching compiler toolchain")
1638 # Run static compilation units now (collect, vmt..)
1639 filepath
= bpy
.data
.filepath
1640 directory
= os
.path
.dirname(filepath
)
1641 settings
= bpy
.context
.scene
.cxr_data
1643 asset_dir
= F
"{directory}/modelsrc"
1644 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1645 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1646 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1648 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
1649 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
1650 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
1651 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
1653 os
.makedirs( asset_dir
, exist_ok
=True )
1654 os
.makedirs( material_dir
, exist_ok
=True )
1655 os
.makedirs( model_dir
, exist_ok
=True )
1657 static
.FILE
= open(F
"/tmp/convexer_compile_log.txt","w")
1660 sceneinfo
= cxr_scene_collect()
1666 for brush
in sceneinfo
['geo']:
1667 for ms
in brush
['object'].material_slots
:
1668 a_materials
.add( ms
.material
)
1669 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1670 errmat
= ms
.material
.name
1671 errnam
= brush
['object'].name
1672 print( F
"Vertex shader {errmat} used on {errnam}")
1673 return {'CANCELLED'}
1677 for ent
in sceneinfo
['entities']:
1678 if ent
['object'] == None: continue
1680 if ent
['classname'] == 'prop_static':
1682 if isinstance(obj
,bpy
.types
.Collection
):
1684 a_models
.add( target
)
1685 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
1686 settings
.project_name
, ent
['transform'])]
1688 target
= obj
.instance_collection
1689 if target
in a_models
:
1691 a_models
.add( target
)
1693 # TODO: Should take into account collection instancing offset
1694 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
1695 settings
.project_name
, ent
['transform'])]
1697 elif ent
['object'].type == 'MESH':
1698 for ms
in ent
['object'].material_slots
:
1699 a_materials
.add( ms
.material
)
1701 for mdl
in a_models
:
1702 uid
= asset_uid(mdl
)
1703 qc_jobs
+= [F
'{uid}.qc']
1705 for obj
in mdl
.objects
:
1706 for ms
in obj
.material_slots
:
1707 a_materials
.add( ms
.material
)
1708 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1709 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1711 errmat
= ms
.material
.name
1713 print( F
"Lightmapped shader {errmat} used on {errnam}")
1714 return {'CANCELLED'}
1717 for mat
in a_materials
:
1718 for pair
in compile_material(mat
):
1723 if isinstance(prop
,bpy
.types
.Image
):
1725 if 'flags' in pdef
: flags
= pdef
['flags']
1726 if prop
not in image_jobs
:
1727 image_jobs
+= [(prop
,)]
1728 prop
.cxr_data
.flags
= flags
1731 with
open( packlist
, "w" ) as fp
:
1733 for mat
in a_materials
:
1734 if mat
.cxr_data
.shader
== 'Builtin': continue
1735 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
1736 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
1738 for img_job
in image_jobs
:
1740 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
1741 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vmt\n")
1743 for mdl
in a_models
:
1744 local
= asset_path('models',mdl
)
1745 winep
= cxr_winepath(asset_full_path('models',mdl
))
1747 fp
.write(F
"{local}.vvd\n")
1748 fp
.write(F
"{winep}.vvd\n")
1749 fp
.write(F
"{local}.dx90.vtx\n")
1750 fp
.write(F
"{winep}.dx90.vtx\n")
1751 fp
.write(F
"{local}.mdl\n")
1752 fp
.write(F
"{winep}.mdl\n")
1753 fp
.write(F
"{local}.vvd\n")
1754 fp
.write(F
"{winep}.vvd\n")
1760 if settings
.comp_vmf
:
1761 static
.JOBINFO
+= [{
1762 "title": "Convexer",
1764 "colour": (1.0,0.3,0.1,1.0),
1765 "exec": cxr_export_vmf
,
1766 "jobs": [(sceneinfo
,output_vmf
)]
1769 if settings
.comp_textures
:
1770 if len(image_jobs
) > 0:
1771 static
.JOBINFO
+= [{
1772 "title": "Textures",
1774 "colour": (0.1,1.0,0.3,1.0),
1775 "exec": compile_image
,
1779 game
= cxr_winepath( settings
.subdir
)
1781 '-game', game
, settings
.project_name
1785 if settings
.comp_models
:
1786 if len(model_jobs
) > 0:
1787 static
.JOBINFO
+= [{
1790 "colour": (0.5,0.5,1.0,1.0),
1791 "exec": cxr_export_modelsrc
,
1795 if len(qc_jobs
) > 0:
1796 static
.JOBINFO
+= [{
1797 "title": "StudioMDL",
1799 "colour": (0.8,0.1,0.1,1.0),
1800 "exec": "studiomdl",
1801 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1802 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1807 if settings
.comp_compile
:
1808 static
.JOBINFO
+= [{
1811 "colour": (0.1,0.2,1.0,1.0),
1813 "jobs": [[settings
[F
'exe_vbsp']] + args
],
1817 static
.JOBINFO
+= [{
1820 "colour": (0.9,0.5,0.5,1.0),
1822 "jobs": [[settings
[F
'exe_vvis']] + ['-fast'] + args
],
1826 vrad_opt
= settings
.opt_vrad
.split()
1827 static
.JOBINFO
+= [{
1830 "colour": (0.9,0.2,0.3,1.0),
1832 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
1836 static
.JOBINFO
+= [{
1839 "colour": (0.0,1.0,0.4,1.0),
1840 "exec": cxr_patchmap
,
1841 "jobs": [(bsp_local
,bsp_remote
)]
1844 if settings
.comp_pack
:
1845 static
.JOBINFO
+= [{
1848 "colour": (0.2,0.2,0.2,1.0),
1850 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
1851 cxr_winepath(bsp_remote
),
1852 cxr_winepath(packlist
),
1853 cxr_winepath(bsp_packed
) ]],
1857 if len(static
.JOBINFO
) == 0:
1858 return {'CANCELLED'}
1860 static
.USER_EXIT
=False
1861 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
1862 wm
.modal_handler_add(_
)
1864 cxr_jobs_update_graph( static
.JOBINFO
)
1866 return {'RUNNING_MODAL'}
1868 class CXR_RESET_HASHES(bpy
.types
.Operator
):
1869 bl_idname
="convexer.hash_reset"
1870 bl_label
="Reset asset hashes"
1872 def execute(_
,context
):
1873 for c
in bpy
.data
.collections
:
1874 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1875 c
.cxr_data
.asset_id
=0
1877 for t
in bpy
.data
.images
:
1878 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1879 t
.cxr_data
.asset_id
=0
1883 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
1884 bl_idname
="convexer.matcomp"
1885 bl_label
="Recompile Material"
1887 def execute(_
,context
):
1888 active_obj
= bpy
.context
.active_object
1889 active_mat
= active_obj
.active_material
1891 #TODO: reduce code dupe (L1663)
1892 for pair
in compile_material(active_mat
):
1897 if isinstance(prop
,bpy
.types
.Image
):
1899 if 'flags' in pdef
: flags
= pdef
['flags']
1900 prop
.cxr_data
.flags
= flags
1902 compile_image( prop
)
1904 settings
= bpy
.context
.scene
.cxr_data
1905 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
1906 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
1909 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
1910 o
.write('sv_cheats 1\n')
1911 o
.write('mp_warmup_pausetimer 1\n')
1912 o
.write('bot_kick\n')
1913 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
1918 # ------------------------------------------------------------------------------
1920 # Helper buttons for 3d toolbox view
1922 class CXR_VIEW3D( bpy
.types
.Panel
):
1923 bl_idname
= "VIEW3D_PT_convexer"
1924 bl_label
= "Convexer"
1925 bl_space_type
= 'VIEW_3D'
1926 bl_region_type
= 'UI'
1927 bl_category
= "Convexer"
1930 def poll(cls
, context
):
1931 return (context
.object is not None)
1933 def draw(_
, context
):
1937 row
.operator("convexer.preview")
1939 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
1941 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
1943 # Main scene properties interface, where all the settings go
1945 class CXR_INTERFACE(bpy
.types
.Panel
):
1947 bl_idname
="SCENE_PT_convexer"
1948 bl_space_type
='PROPERTIES'
1949 bl_region_type
='WINDOW'
1952 def draw(_
,context
):
1953 _
.layout
.operator("convexer.reload")
1954 _
.layout
.operator("convexer.dev_test")
1955 _
.layout
.operator("convexer.preview")
1956 _
.layout
.operator("convexer.hash_reset")
1958 settings
= context
.scene
.cxr_data
1960 _
.layout
.prop(settings
, "debug")
1961 _
.layout
.prop(settings
, "scale_factor")
1962 _
.layout
.prop(settings
, "skybox_scale_factor")
1963 _
.layout
.prop(settings
, "skyname" )
1964 _
.layout
.prop(settings
, "lightmap_scale")
1965 _
.layout
.prop(settings
, "light_scale" )
1966 _
.layout
.prop(settings
, "image_quality" )
1968 box
= _
.layout
.box()
1970 box
.prop(settings
, "project_name")
1971 box
.prop(settings
, "subdir")
1973 box
= _
.layout
.box()
1974 box
.operator("convexer.detect_compilers")
1975 box
.prop(settings
, "exe_studiomdl")
1976 box
.prop(settings
, "exe_vbsp")
1977 box
.prop(settings
, "exe_vvis")
1978 box
.prop(settings
, "exe_vrad")
1979 box
.prop(settings
, "opt_vrad")
1983 row
.prop(settings
,"comp_vmf")
1984 row
.prop(settings
,"comp_textures")
1985 row
.prop(settings
,"comp_models")
1986 row
.prop(settings
,"comp_compile")
1987 row
.prop(settings
,"comp_pack")
1989 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
1992 row
.operator("convexer.chain", text
=text
)
1995 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1996 bl_label
="VMT Properties"
1997 bl_idname
="SCENE_PT_convexer_vmt"
1998 bl_space_type
='PROPERTIES'
1999 bl_region_type
='WINDOW'
2000 bl_context
="material"
2002 def draw(_
,context
):
2003 active_object
= bpy
.context
.active_object
2004 if active_object
== None: return
2006 active_material
= active_object
.active_material
2007 if active_material
== None: return
2009 properties
= active_material
.cxr_data
2010 info
= material_info( active_material
)
2012 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2013 row
= _
.layout
.row()
2014 row
.prop( properties
, "shader" )
2015 row
.operator( "convexer.matcomp" )
2017 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2019 def _mtex( name
, img
, uiParent
):
2022 box
= uiParent
.box()
2023 box
.label( text
=F
'{name} "{img.filepath}"' )
2025 if ((x
& (x
- 1)) == 0):
2028 closest_diff
= 10000000
2030 dist
= abs((1 << i
)-x
)
2031 if dist
< closest_diff
:
2036 return 1 << (closest
+1)
2038 return 1 << (closest
-1)
2043 row
.prop( img
.cxr_data
, "export_res" )
2044 row
.prop( img
.cxr_data
, "fmt" )
2047 row
.prop( img
.cxr_data
, "mipmap" )
2048 row
.prop( img
.cxr_data
, "lod" )
2049 row
.prop( img
.cxr_data
, "clamp" )
2051 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2052 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2054 def _mview( layer
, uiParent
):
2058 if isinstance(layer
[decl
],dict): # $property definition
2060 ptype
= pdef
['type']
2066 if ('shaders' in pdef
) and \
2067 (properties
.shader
not in pdef
['shaders']):
2070 if ptype
== 'intrinsic':
2071 if decl
not in info
:
2076 if isinstance(pdef
[ch
],dict):
2077 if ptype
== 'ui' or ptype
== 'intrinsic':
2079 elif getattr(properties
,decl
) == pdef
['default']:
2082 thisnode
= uiParent
.box()
2086 thisnode
.label( text
=decl
)
2087 elif ptype
== 'intrinsic':
2088 if isinstance(info
[decl
], bpy
.types
.Image
):
2089 _mtex( decl
, info
[decl
], thisnode
)
2091 # hidden intrinsic value.
2092 # Means its a float array or something not an image
2093 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2095 thisnode
.prop(properties
,decl
)
2096 if expandview
: _mview(pdef
,thisnode
)
2098 _mview( cxr_shader_params
, _
.layout
)
2100 def cxr_entity_changeclass(_
,context
):
2101 active_object
= context
.active_object
2103 # Create ID properties
2105 classname
= cxr_custom_class(active_object
)
2107 if classname
in cxr_entities
:
2108 entdef
= cxr_entities
[classname
]
2110 kvs
= entdef
['keyvalues']
2111 if callable(kvs
): kvs
= kvs(active_object
)
2117 if callable(kv
) or not isinstance(kv
,dict): continue
2119 if key
not in active_object
:
2120 active_object
[key
] = kv
['default']
2121 id_prop
= active_object
.id_properties_ui(key
)
2122 id_prop
.update(default
=kv
['default'])
2124 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2125 bl_label
="Entity Config"
2126 bl_idname
="SCENE_PT_convexer_entity"
2127 bl_space_type
='PROPERTIES'
2128 bl_region_type
='WINDOW'
2131 def draw(_
,context
):
2132 active_object
= bpy
.context
.active_object
2134 if active_object
== None: return
2137 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2141 ecn
= cxr_intrinsic_classname( active_object
)
2142 classname
= cxr_custom_class( active_object
)
2145 if active_object
.type == 'MESH':
2146 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2147 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2149 if classname
== 'NONE':
2152 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2153 _
.layout
.enabled
=False
2156 kvs
= cxr_entity_keyvalues( {
2157 "object": active_object
,
2158 "transform": default_context
,
2159 "classname": classname
2165 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2167 row
= _
.layout
.row()
2169 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2171 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2173 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2174 bl_label
= "Source Settings"
2175 bl_idname
= "LIGHT_PT_cxr"
2176 bl_space_type
= 'PROPERTIES'
2177 bl_region_type
= 'WINDOW'
2180 def draw(self
, context
):
2181 layout
= self
.layout
2182 scene
= context
.scene
2184 active_object
= bpy
.context
.active_object
2185 if active_object
== None: return
2187 if active_object
.type == 'LIGHT' or \
2188 active_object
.type == 'LIGHT_PROBE':
2190 properties
= active_object
.data
.cxr_data
2192 if active_object
.type == 'LIGHT':
2193 layout
.prop( properties
, "realtime" )
2194 elif active_object
.type == 'LIGHT_PROBE':
2195 layout
.prop( properties
, "size" )
2198 # ------------------------------------------------------------------------------
2200 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2201 export_res
: bpy
.props
.IntVectorProperty(
2203 description
="Texture Export Resolution",
2209 fmt
: bpy
.props
.EnumProperty(
2212 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2213 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2214 ('RGB', "RGB", "Uncompressed", '', 2),
2215 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2217 description
="Image format",
2220 last_hash
: bpy
.props
.StringProperty( name
="" )
2221 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2223 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2224 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2225 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2226 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2228 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2229 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2231 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2232 size
: bpy
.props
.EnumProperty(
2235 ('1',"1x1",'','',0),
2236 ('2',"2x2",'','',1),
2237 ('3',"4x4",'','',2),
2238 ('4',"8x8",'','',3),
2239 ('5',"16x16",'','',4),
2240 ('6',"32x32",'','',5),
2241 ('7',"64x64",'','',6),
2242 ('8',"128x128",'','',7),
2243 ('9',"256x256",'','',8)
2245 description
="Texture resolution",
2248 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2249 entity
: bpy
.props
.BoolProperty(name
="")
2251 enum_pointents
= [('NONE',"None","")]
2252 enum_brushents
= [('NONE',"None","")]
2254 for classname
in cxr_entities
:
2255 entdef
= cxr_entities
[classname
]
2256 if 'allow' in entdef
:
2257 itm
= [(classname
, classname
, "")]
2258 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2259 else: enum_brushents
+= itm
2261 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2262 update
=cxr_entity_changeclass
, default
='NONE' )
2264 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2265 update
=cxr_entity_changeclass
, default
='NONE' )
2267 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2268 last_hash
: bpy
.props
.StringProperty( name
="" )
2269 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2271 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2272 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2273 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
2275 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2276 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2277 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2278 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2279 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2280 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2281 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2282 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2284 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
2285 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2286 default
=32.0,min=1.0)
2287 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2288 default
=1.0,min=0.01)
2289 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2290 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2291 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2292 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2294 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2296 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2297 default
=8, min=0, max=18 )
2299 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2300 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2301 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2302 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2303 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2305 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2306 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2307 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2308 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2309 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2310 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2311 CXR_COMPILE_MATERIAL
]
2313 vmt_param_dynamic_class
= None
2316 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2319 bpy
.utils
.register_class(c
)
2321 # Build dynamic VMT properties class defined by cxr_shader_params
2322 annotations_dict
= {}
2324 def _dvmt_propogate(layer
):
2325 nonlocal annotations_dict
2328 if isinstance(layer
[decl
],dict): # $property definition
2332 if pdef
['type'] == 'bool':
2333 prop
= bpy
.props
.BoolProperty(\
2334 name
= pdef
['name'],\
2335 default
= pdef
['default'])
2337 elif pdef
['type'] == 'float':
2338 prop
= bpy
.props
.FloatProperty(\
2339 name
= pdef
['name'],\
2340 default
= pdef
['default'])
2342 elif pdef
['type'] == 'vector':
2343 if 'subtype' in pdef
:
2344 prop
= bpy
.props
.FloatVectorProperty(\
2345 name
= pdef
['name'],\
2346 subtype
= pdef
['subtype'],\
2347 default
= pdef
['default'],\
2348 size
= len(pdef
['default']))
2350 prop
= bpy
.props
.FloatVectorProperty(\
2351 name
= pdef
['name'],\
2352 default
= pdef
['default'],\
2353 size
= len(pdef
['default']))
2355 elif pdef
['type'] == 'string':
2356 prop
= bpy
.props
.StringProperty(\
2357 name
= pdef
['name'],\
2358 default
= pdef
['default'])
2360 elif pdef
['type'] == 'enum':
2361 prop
= bpy
.props
.EnumProperty(\
2362 name
= pdef
['name'],\
2363 items
= pdef
['items'],\
2364 default
= pdef
['default'])
2367 annotations_dict
[decl
] = prop
2369 # Recurse into sub-definitions
2370 _dvmt_propogate(pdef
)
2372 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2375 cxr_shaders
[_
]["name"],\
2376 '') for _
in cxr_shaders
],\
2377 default
= next(iter(cxr_shaders
)))
2379 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2382 _dvmt_propogate( cxr_shader_params
)
2383 vmt_param_dynamic_class
= type(
2385 (bpy
.types
.PropertyGroup
,),{
2386 "__annotations__": annotations_dict
2390 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2393 bpy
.types
.Material
.cxr_data
= \
2394 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2395 bpy
.types
.Image
.cxr_data
= \
2396 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2397 bpy
.types
.Object
.cxr_data
= \
2398 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2399 bpy
.types
.Collection
.cxr_data
= \
2400 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2401 bpy
.types
.Light
.cxr_data
= \
2402 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2403 bpy
.types
.LightProbe
.cxr_data
= \
2404 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2405 bpy
.types
.Scene
.cxr_data
= \
2406 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2408 # CXR Scene settings
2411 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2412 cxr_draw
,(),'WINDOW','POST_VIEW')
2414 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2415 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2417 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2418 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2421 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2423 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2425 bpy
.utils
.unregister_class(c
)
2427 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2428 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2430 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2431 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')