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 # Main compile function
1502 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1503 bl_idname
="convexer.chain"
1504 bl_label
="Compile Chain"
1519 def cancel(_
,context
):
1520 global cxr_jobs_batch
1521 static
= _
.__class
__
1522 wm
= context
.window_manager
1524 if static
.SUBPROC
!= None:
1525 static
.SUBPROC
.terminate()
1526 static
.SUBPROC
= None
1528 if static
.TIMER
!= None:
1529 wm
.event_timer_remove( static
.TIMER
)
1534 cxr_jobs_batch
= None
1538 def modal(_
,context
,ev
):
1539 static
= _
.__class
__
1541 if ev
.type == 'TIMER':
1542 global cxr_jobs_batch
1544 if static
.WAIT_REDRAW
:
1546 return {'PASS_THROUGH'}
1547 static
.WAIT_REDRAW
= True
1549 if static
.USER_EXIT
:
1550 print( "Chain USER_EXIT" )
1551 return _
.cancel(context
)
1553 if static
.SUBPROC
!= None:
1554 # Deal with async modes
1555 status
= static
.SUBPROC
.poll()
1558 # Cannot redirect STDOUT through here without causing
1559 # undefined behaviour due to the Blender Python specification.
1561 # Have to write it out to a file and read it back in.
1563 with
open("/tmp/convexer_compile_log.txt","r") as log
:
1564 static
.LOG
= log
.readlines()
1565 return {'PASS_THROUGH'}
1567 #for l in static.SUBPROC.stdout:
1568 # print( F'-> {l.decode("utf-8")}',end='' )
1569 static
.SUBPROC
= None
1572 print(F
'Compiler () error: {status}')
1573 return _
.cancel(context
)
1575 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1576 cxr_jobs_update_graph( static
.JOBINFO
)
1578 return {'PASS_THROUGH'}
1580 # Compile syncronous thing
1581 for sys
in static
.JOBINFO
:
1582 for i
,target
in enumerate(sys
['jobs']):
1585 if callable(sys
['exec']):
1586 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1588 if not sys
['exec'](*target
):
1589 print( "Job failed" )
1590 return _
.cancel(context
)
1592 sys
['jobs'][i
] = None
1595 # Run external executable (wine)
1596 static
.SUBPROC
= subprocess
.Popen( target
,
1597 stdout
=static
.FILE
,\
1598 stderr
=subprocess
.PIPE
,\
1603 cxr_jobs_update_graph( static
.JOBINFO
)
1605 return {'PASS_THROUGH'}
1608 print( "All jobs completed!" )
1609 cxr_jobs_batch
= None
1612 return _
.cancel(context
)
1614 return {'PASS_THROUGH'}
1616 def invoke(_
,context
,event
):
1617 static
= _
.__class
__
1618 wm
= context
.window_manager
1620 if static
.TIMER
== None:
1621 print("Launching compiler toolchain")
1623 # Run static compilation units now (collect, vmt..)
1624 filepath
= bpy
.data
.filepath
1625 directory
= os
.path
.dirname(filepath
)
1626 settings
= bpy
.context
.scene
.cxr_data
1628 asset_dir
= F
"{directory}/modelsrc"
1629 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1630 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1631 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1633 os
.makedirs( asset_dir
, exist_ok
=True )
1634 os
.makedirs( material_dir
, exist_ok
=True )
1635 os
.makedirs( model_dir
, exist_ok
=True )
1637 static
.FILE
= open(F
"/tmp/convexer_compile_log.txt","w")
1640 sceneinfo
= cxr_scene_collect()
1646 for brush
in sceneinfo
['geo']:
1647 for ms
in brush
['object'].material_slots
:
1648 a_materials
.add( ms
.material
)
1649 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1650 errmat
= ms
.material
.name
1651 errnam
= brush
['object'].name
1652 print( F
"Vertex shader {errmat} used on {errnam}")
1653 return {'CANCELLED'}
1657 for ent
in sceneinfo
['entities']:
1658 if ent
['object'] == None: continue
1660 if ent
['classname'] == 'prop_static':
1662 if isinstance(obj
,bpy
.types
.Collection
):
1664 a_models
.add( target
)
1665 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
1666 settings
.project_name
, ent
['transform'])]
1668 target
= obj
.instance_collection
1669 if target
in a_models
:
1671 a_models
.add( target
)
1673 # TODO: Should take into account collection instancing offset
1674 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
1675 settings
.project_name
, ent
['transform'])]
1677 elif ent
['object'].type == 'MESH':
1678 for ms
in ent
['object'].material_slots
:
1679 a_materials
.add( ms
.material
)
1681 for mdl
in a_models
:
1682 uid
= asset_uid(mdl
)
1683 qc_jobs
+= [F
'{uid}.qc']
1685 for obj
in mdl
.objects
:
1686 for ms
in obj
.material_slots
:
1687 a_materials
.add( ms
.material
)
1688 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1689 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1691 errmat
= ms
.material
.name
1693 print( F
"Lightmapped shader {errmat} used on {errnam}")
1694 return {'CANCELLED'}
1697 for mat
in a_materials
:
1698 for pair
in compile_material(mat
):
1703 if isinstance(prop
,bpy
.types
.Image
):
1705 if 'flags' in pdef
: flags
= pdef
['flags']
1706 if prop
not in image_jobs
:
1707 image_jobs
+= [(prop
,)]
1708 prop
.cxr_data
.flags
= flags
1714 if settings
.comp_vmf
:
1715 static
.JOBINFO
+= [{
1716 "title": "Convexer",
1718 "colour": (1.0,0.3,0.1,1.0),
1719 "exec": cxr_export_vmf
,
1720 "jobs": [(sceneinfo
,output_vmf
)]
1723 if settings
.comp_textures
:
1724 if len(image_jobs
) > 0:
1725 static
.JOBINFO
+= [{
1726 "title": "Textures",
1728 "colour": (0.1,1.0,0.3,1.0),
1729 "exec": compile_image
,
1733 game
= 'z:'+settings
.subdir
.replace('/','\\')
1735 '-game', game
, settings
.project_name
1739 if settings
.comp_models
:
1740 if len(model_jobs
) > 0:
1741 static
.JOBINFO
+= [{
1744 "colour": (0.5,0.5,1.0,1.0),
1745 "exec": cxr_export_modelsrc
,
1749 if len(qc_jobs
) > 0:
1750 static
.JOBINFO
+= [{
1751 "title": "StudioMDL",
1753 "colour": (0.8,0.1,0.1,1.0),
1754 "exec": "studiomdl",
1755 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1756 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1761 if settings
.comp_compile
:
1762 static
.JOBINFO
+= [{
1765 "colour": (0.1,0.2,1.0,1.0),
1767 "jobs": [[settings
[F
'exe_vbsp']] + args
],
1771 static
.JOBINFO
+= [{
1774 "colour": (0.9,0.5,0.5,1.0),
1776 "jobs": [[settings
[F
'exe_vvis']] + ['-fast'] + args
],
1780 vrad_opt
= settings
.opt_vrad
.split()
1781 static
.JOBINFO
+= [{
1784 "colour": (0.9,0.2,0.3,1.0),
1786 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
1790 static
.JOBINFO
+= [{
1793 "colour": (0.0,1.0,0.4,1.0),
1794 "exec": cxr_patchmap
,
1795 "jobs": [(F
"{directory}/{settings.project_name}.bsp",\
1796 F
"{settings.subdir}/maps/{settings.project_name}.bsp")]
1799 static
.USER_EXIT
=False
1800 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
1801 wm
.modal_handler_add(_
)
1803 cxr_jobs_update_graph( static
.JOBINFO
)
1805 return {'RUNNING_MODAL'}
1807 print("Chain exiting...")
1808 static
.USER_EXIT
=True
1809 return {'RUNNING_MODAL'}
1811 class CXR_RESET_HASHES(bpy
.types
.Operator
):
1812 bl_idname
="convexer.hash_reset"
1813 bl_label
="Reset asset hashes"
1815 def execute(_
,context
):
1816 for c
in bpy
.data
.collections
:
1817 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1818 c
.cxr_data
.asset_id
=0
1820 for t
in bpy
.data
.images
:
1821 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1822 t
.cxr_data
.asset_id
=0
1826 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
1827 bl_idname
="convexer.matcomp"
1828 bl_label
="Recompile Material"
1830 def execute(_
,context
):
1831 active_obj
= bpy
.context
.active_object
1832 active_mat
= active_obj
.active_material
1834 #TODO: reduce code dupe (L1663)
1835 for pair
in compile_material(active_mat
):
1840 if isinstance(prop
,bpy
.types
.Image
):
1842 if 'flags' in pdef
: flags
= pdef
['flags']
1843 prop
.cxr_data
.flags
= flags
1845 compile_image( prop
)
1847 settings
= bpy
.context
.scene
.cxr_data
1848 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
1849 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
1852 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
1853 o
.write('sv_cheats 1\n')
1854 o
.write('mp_warmup_pausetimer 1\n')
1855 o
.write('bot_kick\n')
1856 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
1861 # ------------------------------------------------------------------------------
1863 # Helper buttons for 3d toolbox view
1865 class CXR_VIEW3D( bpy
.types
.Panel
):
1866 bl_idname
= "VIEW3D_PT_convexer"
1867 bl_label
= "Convexer"
1868 bl_space_type
= 'VIEW_3D'
1869 bl_region_type
= 'UI'
1870 bl_category
= "Convexer"
1873 def poll(cls
, context
):
1874 return (context
.object is not None)
1876 def draw(_
, context
):
1880 row
.operator("convexer.preview")
1882 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
1884 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
1886 # Main scene properties interface, where all the settings go
1888 class CXR_INTERFACE(bpy
.types
.Panel
):
1890 bl_idname
="SCENE_PT_convexer"
1891 bl_space_type
='PROPERTIES'
1892 bl_region_type
='WINDOW'
1895 def draw(_
,context
):
1896 _
.layout
.operator("convexer.reload")
1897 _
.layout
.operator("convexer.dev_test")
1898 _
.layout
.operator("convexer.preview")
1899 _
.layout
.operator("convexer.hash_reset")
1901 settings
= context
.scene
.cxr_data
1903 _
.layout
.prop(settings
, "debug")
1904 _
.layout
.prop(settings
, "scale_factor")
1905 _
.layout
.prop(settings
, "skybox_scale_factor")
1906 _
.layout
.prop(settings
, "skyname" )
1907 _
.layout
.prop(settings
, "lightmap_scale")
1908 _
.layout
.prop(settings
, "light_scale" )
1909 _
.layout
.prop(settings
, "image_quality" )
1911 box
= _
.layout
.box()
1913 box
.prop(settings
, "project_name")
1914 box
.prop(settings
, "subdir")
1916 box
= _
.layout
.box()
1917 box
.operator("convexer.detect_compilers")
1918 box
.prop(settings
, "exe_studiomdl")
1919 box
.prop(settings
, "exe_vbsp")
1920 box
.prop(settings
, "exe_vvis")
1921 box
.prop(settings
, "exe_vrad")
1922 box
.prop(settings
, "opt_vrad")
1926 row
.prop(settings
,"comp_vmf")
1927 row
.prop(settings
,"comp_textures")
1928 row
.prop(settings
,"comp_models")
1929 row
.prop(settings
,"comp_compile")
1931 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
1934 row
.operator("convexer.chain", text
=text
)
1937 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1938 bl_label
="VMT Properties"
1939 bl_idname
="SCENE_PT_convexer_vmt"
1940 bl_space_type
='PROPERTIES'
1941 bl_region_type
='WINDOW'
1942 bl_context
="material"
1944 def draw(_
,context
):
1945 active_object
= bpy
.context
.active_object
1946 if active_object
== None: return
1948 active_material
= active_object
.active_material
1949 if active_material
== None: return
1951 properties
= active_material
.cxr_data
1952 info
= material_info( active_material
)
1954 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1955 row
= _
.layout
.row()
1956 row
.prop( properties
, "shader" )
1957 row
.operator( "convexer.matcomp" )
1959 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
1961 def _mtex( name
, img
, uiParent
):
1964 box
= uiParent
.box()
1965 box
.label( text
=F
'{name} "{img.filepath}"' )
1967 if ((x
& (x
- 1)) == 0):
1970 closest_diff
= 10000000
1972 dist
= abs((1 << i
)-x
)
1973 if dist
< closest_diff
:
1978 return 1 << (closest
+1)
1980 return 1 << (closest
-1)
1985 row
.prop( img
.cxr_data
, "export_res" )
1986 row
.prop( img
.cxr_data
, "fmt" )
1989 row
.prop( img
.cxr_data
, "mipmap" )
1990 row
.prop( img
.cxr_data
, "lod" )
1991 row
.prop( img
.cxr_data
, "clamp" )
1993 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1994 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1996 def _mview( layer
, uiParent
):
2000 if isinstance(layer
[decl
],dict): # $property definition
2002 ptype
= pdef
['type']
2008 if ('shaders' in pdef
) and \
2009 (properties
.shader
not in pdef
['shaders']):
2012 if ptype
== 'intrinsic':
2013 if decl
not in info
:
2018 if isinstance(pdef
[ch
],dict):
2019 if ptype
== 'ui' or ptype
== 'intrinsic':
2021 elif getattr(properties
,decl
) == pdef
['default']:
2024 thisnode
= uiParent
.box()
2028 thisnode
.label( text
=decl
)
2029 elif ptype
== 'intrinsic':
2030 if isinstance(info
[decl
], bpy
.types
.Image
):
2031 _mtex( decl
, info
[decl
], thisnode
)
2033 # hidden intrinsic value.
2034 # Means its a float array or something not an image
2035 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2037 thisnode
.prop(properties
,decl
)
2038 if expandview
: _mview(pdef
,thisnode
)
2040 _mview( cxr_shader_params
, _
.layout
)
2042 def cxr_entity_changeclass(_
,context
):
2043 active_object
= context
.active_object
2045 # Create ID properties
2047 classname
= cxr_custom_class(active_object
)
2049 if classname
in cxr_entities
:
2050 entdef
= cxr_entities
[classname
]
2052 kvs
= entdef
['keyvalues']
2053 if callable(kvs
): kvs
= kvs(active_object
)
2059 if callable(kv
) or not isinstance(kv
,dict): continue
2061 if key
not in active_object
:
2062 active_object
[key
] = kv
['default']
2063 id_prop
= active_object
.id_properties_ui(key
)
2064 id_prop
.update(default
=kv
['default'])
2066 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2067 bl_label
="Entity Config"
2068 bl_idname
="SCENE_PT_convexer_entity"
2069 bl_space_type
='PROPERTIES'
2070 bl_region_type
='WINDOW'
2073 def draw(_
,context
):
2074 active_object
= bpy
.context
.active_object
2076 if active_object
== None: return
2079 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2083 ecn
= cxr_intrinsic_classname( active_object
)
2084 classname
= cxr_custom_class( active_object
)
2087 if active_object
.type == 'MESH':
2088 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2089 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2091 if classname
== 'NONE':
2094 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2095 _
.layout
.enabled
=False
2098 kvs
= cxr_entity_keyvalues( {
2099 "object": active_object
,
2100 "transform": default_context
,
2101 "classname": classname
2107 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2109 row
= _
.layout
.row()
2111 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2113 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2115 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2116 bl_label
= "Source Settings"
2117 bl_idname
= "LIGHT_PT_cxr"
2118 bl_space_type
= 'PROPERTIES'
2119 bl_region_type
= 'WINDOW'
2122 def draw(self
, context
):
2123 layout
= self
.layout
2124 scene
= context
.scene
2126 active_object
= bpy
.context
.active_object
2127 if active_object
== None: return
2129 if active_object
.type == 'LIGHT' or \
2130 active_object
.type == 'LIGHT_PROBE':
2132 properties
= active_object
.data
.cxr_data
2134 if active_object
.type == 'LIGHT':
2135 layout
.prop( properties
, "realtime" )
2136 elif active_object
.type == 'LIGHT_PROBE':
2137 layout
.prop( properties
, "size" )
2140 # ------------------------------------------------------------------------------
2142 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2143 export_res
: bpy
.props
.IntVectorProperty(
2145 description
="Texture Export Resolution",
2151 fmt
: bpy
.props
.EnumProperty(
2154 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2155 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2156 ('RGB', "RGB", "Uncompressed", '', 2),
2157 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2159 description
="Image format",
2162 last_hash
: bpy
.props
.StringProperty( name
="" )
2163 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2165 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2166 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2167 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2168 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2170 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2171 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2173 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2174 size
: bpy
.props
.EnumProperty(
2177 ('1',"1x1",'','',0),
2178 ('2',"2x2",'','',1),
2179 ('3',"4x4",'','',2),
2180 ('4',"8x8",'','',3),
2181 ('5',"16x16",'','',4),
2182 ('6',"32x32",'','',5),
2183 ('7',"64x64",'','',6),
2184 ('8',"128x128",'','',7),
2185 ('9',"256x256",'','',8)
2187 description
="Texture resolution",
2190 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2191 entity
: bpy
.props
.BoolProperty(name
="")
2193 enum_pointents
= [('NONE',"None","")]
2194 enum_brushents
= [('NONE',"None","")]
2196 for classname
in cxr_entities
:
2197 entdef
= cxr_entities
[classname
]
2198 if 'allow' in entdef
:
2199 itm
= [(classname
, classname
, "")]
2200 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2201 else: enum_brushents
+= itm
2203 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2204 update
=cxr_entity_changeclass
, default
='NONE' )
2206 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2207 update
=cxr_entity_changeclass
, default
='NONE' )
2209 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2210 last_hash
: bpy
.props
.StringProperty( name
="" )
2211 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2213 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2214 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2215 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
2217 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2218 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2219 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2220 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2221 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2222 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2223 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2224 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2226 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
2227 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2228 default
=32.0,min=1.0)
2229 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2230 default
=1.0,min=0.01)
2231 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2232 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2233 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2234 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2236 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2238 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2239 default
=8, min=0, max=18 )
2241 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2242 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2243 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2244 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2246 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2247 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2248 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2249 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2250 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2251 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2252 CXR_COMPILE_MATERIAL
]
2254 vmt_param_dynamic_class
= None
2257 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2260 bpy
.utils
.register_class(c
)
2262 # Build dynamic VMT properties class defined by cxr_shader_params
2263 annotations_dict
= {}
2265 def _dvmt_propogate(layer
):
2266 nonlocal annotations_dict
2269 if isinstance(layer
[decl
],dict): # $property definition
2273 if pdef
['type'] == 'bool':
2274 prop
= bpy
.props
.BoolProperty(\
2275 name
= pdef
['name'],\
2276 default
= pdef
['default'])
2278 elif pdef
['type'] == 'float':
2279 prop
= bpy
.props
.FloatProperty(\
2280 name
= pdef
['name'],\
2281 default
= pdef
['default'])
2283 elif pdef
['type'] == 'vector':
2284 if 'subtype' in pdef
:
2285 prop
= bpy
.props
.FloatVectorProperty(\
2286 name
= pdef
['name'],\
2287 subtype
= pdef
['subtype'],\
2288 default
= pdef
['default'],\
2289 size
= len(pdef
['default']))
2291 prop
= bpy
.props
.FloatVectorProperty(\
2292 name
= pdef
['name'],\
2293 default
= pdef
['default'],\
2294 size
= len(pdef
['default']))
2296 elif pdef
['type'] == 'string':
2297 prop
= bpy
.props
.StringProperty(\
2298 name
= pdef
['name'],\
2299 default
= pdef
['default'])
2301 elif pdef
['type'] == 'enum':
2302 prop
= bpy
.props
.EnumProperty(\
2303 name
= pdef
['name'],\
2304 items
= pdef
['items'],\
2305 default
= pdef
['default'])
2308 annotations_dict
[decl
] = prop
2310 # Recurse into sub-definitions
2311 _dvmt_propogate(pdef
)
2313 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2316 cxr_shaders
[_
]["name"],\
2317 '') for _
in cxr_shaders
],\
2318 default
= next(iter(cxr_shaders
)))
2320 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2323 _dvmt_propogate( cxr_shader_params
)
2324 vmt_param_dynamic_class
= type(
2326 (bpy
.types
.PropertyGroup
,),{
2327 "__annotations__": annotations_dict
2331 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2334 bpy
.types
.Material
.cxr_data
= \
2335 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2336 bpy
.types
.Image
.cxr_data
= \
2337 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2338 bpy
.types
.Object
.cxr_data
= \
2339 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2340 bpy
.types
.Collection
.cxr_data
= \
2341 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2342 bpy
.types
.Light
.cxr_data
= \
2343 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2344 bpy
.types
.LightProbe
.cxr_data
= \
2345 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2346 bpy
.types
.Scene
.cxr_data
= \
2347 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2349 # CXR Scene settings
2352 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2353 cxr_draw
,(),'WINDOW','POST_VIEW')
2355 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2356 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2358 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2359 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2362 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2364 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2366 bpy
.utils
.unregister_class(c
)
2368 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2369 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2371 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2372 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')