1 # Copyright (C) 2022 Harry Godden (hgn)
5 "author": "Harry Godden (hgn)",
12 "category":"Import/Export",
15 print( "Convexer reload" )
17 #from mathutils import *
18 import bpy
, gpu
, math
, os
, time
, mathutils
, blf
, subprocess
, shutil
, hashlib
20 from gpu_extras
.batch
import batch_for_shader
21 from bpy
.app
.handlers
import persistent
23 # GPU and viewport drawing
24 # ------------------------------------------------------------------------------
27 cxr_view_draw_handler
= None
28 cxr_ui_draw_handler
= None
37 cxr_view_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
38 cxr_ui_shader
= gpu
.types
.GPUShader("""
39 uniform mat4 ModelViewProjectionMatrix;
49 gl_Position = ModelViewProjectionMatrix * vec4(aPos.x*scale,aPos.y, 0.0, 1.0);
64 def cxr_ui(_
,context
):
65 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
67 w
= gpu
.state
.viewport_get()[2]
69 cxr_ui_shader
.uniform_float( "scale", w
)
71 if cxr_jobs_batch
!= None:
72 gpu
.state
.blend_set('ALPHA')
73 cxr_jobs_batch
.draw(cxr_ui_shader
)
75 blf
.position(0,2,50,0)
77 blf
.color(0,1.0,1.0,1.0,1.0)
78 blf
.draw(0,"Compiling")
80 for ji
in cxr_jobs_inf
:
81 blf
.position(0,ji
[0]*w
,35,0)
87 for ln
in reversed(CXR_COMPILER_CHAIN
.LOG
[-25:]):
88 blf
.position(0,2,py
,0)
92 #if CXR_PREVIEW_OPERATOR.LASTERR != None:
93 # blf.position(0,2,80,0)
95 # blf.color(0,1.0,0.2,0.2,0.9)
96 # blf.draw(0,"Invalid geometry")
98 # Something is off with TIMER,
99 # this forces the viewport to redraw before we can continue with our
102 CXR_COMPILER_CHAIN
.WAIT_REDRAW
= False
105 global cxr_view_shader
, cxr_view_mesh
, cxr_view_lines
107 cxr_view_shader
.bind()
109 gpu
.state
.depth_mask_set(False)
110 gpu
.state
.line_width_set(1.5)
111 gpu
.state
.face_culling_set('BACK')
112 gpu
.state
.depth_test_set('NONE')
113 gpu
.state
.blend_set('ALPHA')
115 if cxr_view_lines
!= None:
116 cxr_view_lines
.draw( cxr_view_shader
)
118 gpu
.state
.depth_test_set('LESS_EQUAL')
119 gpu
.state
.blend_set('ADDITIVE')
120 if cxr_view_mesh
!= None:
121 cxr_view_mesh
.draw( cxr_view_shader
)
123 def cxr_jobs_update_graph(jobs
):
124 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
134 total_width
+= sys
['w']
143 colour
= sys
['colour']
144 colourwait
= (colour
[0],colour
[1],colour
[2],0.4)
145 colourrun
= (colour
[0]*1.5,colour
[1]*1.5,colour
[2]*1.5,0.5)
146 colourdone
= (colour
[0],colour
[1],colour
[2],1.0)
149 sfsub
= (1.0/(len(jobs
)))*w
153 if j
== None: colour
= colourdone
154 else: colour
= colourwait
156 px
= (cur
+ (i
)*sfsub
) * sf
157 px1
= (cur
+ (i
+1.0)*sfsub
) * sf
160 verts
+= [(px
,0), (px
, h
), (px1
, 0.0), (px1
,h
)]
161 colours
+= [colour
,colour
,colour
,colour
]
162 indices
+= [(ci
+0,ci
+2,ci
+3),(ci
+0,ci
+3,ci
+1)]
165 cxr_jobs_inf
+= [((sf
*cur
), sys
['title'])]
168 cxr_jobs_batch
= batch_for_shader(
169 cxr_ui_shader
, 'TRIS',
170 { "aPos": verts
, "aColour": colours
},
174 # view_layer.update() doesnt seem to work,
175 # tag_redraw() seems to have broken
176 # therefore, change a property
178 ob
= bpy
.context
.scene
.objects
[0]
179 ob
.hide_render
= ob
.hide_render
181 # the 'real' way to refresh the scene
182 for area
in bpy
.context
.window
.screen
.areas
:
183 if area
.type == 'view_3d':
187 # ------------------------------------------------------------------------------
189 # dlclose for reloading modules manually
191 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
192 libc_dlclose
.argtypes
= [c_void_p
]
194 # wrapper for ctypes binding
196 def __init__(_
,name
,argtypes
,restype
):
198 _
.argtypes
= argtypes
203 _
.call
= getattr(so
,_
.name
)
204 _
.call
.argtypes
= _
.argtypes
206 if _
.restype
!= None:
207 _
.call
.restype
= _
.restype
210 # ------------------------------------------------------------------------------
214 # Structure definitions
216 class cxr_edge(Structure
):
217 _fields_
= [("i0",c_int32
),
219 ("freestyle",c_int32
)]
221 class cxr_static_loop(Structure
):
222 _fields_
= [("index",c_int32
),
223 ("edge_index",c_int32
),
226 class cxr_polygon(Structure
):
227 _fields_
= [("loop_start",c_int32
),
228 ("loop_total",c_int32
),
229 ("normal",c_double
* 3),
230 ("center",c_double
* 3),
231 ("material_id",c_int32
)]
233 class cxr_material(Structure
):
234 _fields_
= [("res",c_int32
* 2),
237 class cxr_static_mesh(Structure
):
238 _fields_
= [("vertices",POINTER(c_double
* 3)),
239 ("edges",POINTER(cxr_edge
)),
240 ("loops",POINTER(cxr_static_loop
)),
241 ("polys",POINTER(cxr_polygon
)),
242 ("materials",POINTER(cxr_material
)),
244 ("poly_count",c_int32
),
245 ("vertex_count",c_int32
),
246 ("edge_count",c_int32
),
247 ("loop_count",c_int32
),
248 ("material_count",c_int32
)]
250 class cxr_tri_mesh(Structure
):
251 _fields_
= [("vertices",POINTER(c_double
*3)),
252 ("colours",POINTER(c_double
*4)),
253 ("indices",POINTER(c_int32
)),
254 ("indices_count",c_int32
),
255 ("vertex_count",c_int32
)]
257 class cxr_vmf_context(Structure
):
258 _fields_
= [("mapversion",c_int32
),
259 ("skyname",c_char_p
),
260 ("detailvbsp",c_char_p
),
261 ("detailmaterial",c_char_p
),
263 ("offset",c_double
*3),
264 ("lightmap_scale",c_int32
),
265 ("brush_count",c_int32
),
266 ("entity_count",c_int32
),
267 ("face_count",c_int32
)]
269 # Convert blenders mesh format into CXR's static format (they are very similar)
271 def mesh_cxr_format(obj
):
274 if bpy
.context
.active_object
!= None:
275 orig_state
= obj
.mode
276 if orig_state
!= 'OBJECT':
277 bpy
.ops
.object.mode_set(mode
='OBJECT')
279 dgraph
= bpy
.context
.evaluated_depsgraph_get()
280 data
= obj
.evaluated_get(dgraph
).data
282 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
284 mesh
= cxr_static_mesh()
286 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
287 for i
, vert
in enumerate(data
.vertices
):
288 v
= obj
.matrix_world
@ vert
.co
289 vertex_data
[i
][0] = c_double(v
[0])
290 vertex_data
[i
][1] = c_double(v
[1])
291 vertex_data
[i
][2] = c_double(v
[2])
293 loop_data
= (cxr_static_loop
*len(data
.loops
))()
294 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
296 for i
, poly
in enumerate(data
.polygons
):
297 loop_start
= poly
.loop_start
298 loop_end
= poly
.loop_start
+ poly
.loop_total
299 for loop_index
in range(loop_start
, loop_end
):
300 loop
= data
.loops
[loop_index
]
301 loop_data
[loop_index
].index
= loop
.vertex_index
302 loop_data
[loop_index
].edge_index
= loop
.edge_index
305 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
306 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
307 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
309 loop_data
[loop_index
].uv
[0] = c_double(0.0)
310 loop_data
[loop_index
].uv
[1] = c_double(0.0)
311 center
= obj
.matrix_world
@ poly
.center
312 normal
= mtx_rot
@ poly
.normal
314 polygon_data
[i
].loop_start
= poly
.loop_start
315 polygon_data
[i
].loop_total
= poly
.loop_total
316 polygon_data
[i
].normal
[0] = normal
[0]
317 polygon_data
[i
].normal
[1] = normal
[1]
318 polygon_data
[i
].normal
[2] = normal
[2]
319 polygon_data
[i
].center
[0] = center
[0]
320 polygon_data
[i
].center
[1] = center
[1]
321 polygon_data
[i
].center
[2] = center
[2]
322 polygon_data
[i
].material_id
= poly
.material_index
324 edge_data
= (cxr_edge
*len(data
.edges
))()
326 for i
, edge
in enumerate(data
.edges
):
327 edge_data
[i
].i0
= edge
.vertices
[0]
328 edge_data
[i
].i1
= edge
.vertices
[1]
329 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
331 material_data
= (cxr_material
*len(obj
.material_slots
))()
333 for i
, ms
in enumerate(obj
.material_slots
):
334 inf
= material_info(ms
.material
)
335 material_data
[i
].res
[0] = inf
['res'][0]
336 material_data
[i
].res
[1] = inf
['res'][1]
337 material_data
[i
].name
= inf
['name'].encode('utf-8')
339 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
340 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
341 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
342 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
343 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
345 mesh
.poly_count
= len(data
.polygons
)
346 mesh
.vertex_count
= len(data
.vertices
)
347 mesh
.edge_count
= len(data
.edges
)
348 mesh
.loop_count
= len(data
.loops
)
349 mesh
.material_count
= len(obj
.material_slots
)
351 if orig_state
!= None:
352 bpy
.ops
.object.mode_set(mode
=orig_state
)
356 # Callback ctypes indirection things.. not really sure.
357 c_libcxr_log_callback
= None
358 c_libcxr_line_callback
= None
361 # -------------------------------------------------------------
362 libcxr_decompose
= extern( "cxr_decompose",
363 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
366 libcxr_free_world
= extern( "cxr_free_world",
370 libcxr_write_test_data
= extern( "cxr_write_test_data",
371 [POINTER(cxr_static_mesh
)],
374 libcxr_world_preview
= extern( "cxr_world_preview",
376 POINTER(cxr_tri_mesh
)
378 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
382 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
383 [POINTER(cxr_vmf_context
), c_void_p
],
386 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
387 [POINTER(cxr_vmf_context
), c_void_p
],
390 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
391 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
394 libcxr_end_vmf
= extern( "cxr_end_vmf",
395 [POINTER(cxr_vmf_context
),c_void_p
],
399 # VDF + with open wrapper
400 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
401 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
402 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
403 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
404 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
405 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
407 class vdf_structure():
408 def __init__(_
,path
):
411 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
413 print( F
"Could not open file {_.path}" )
416 def __exit__(_
,type,value
,traceback
):
418 libcxr_vdf_close
.call(_
.fp
)
420 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
422 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
424 libcxr_vdf_edon
.call(_
.fp
)
426 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
429 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
431 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
432 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
433 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
434 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
435 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
436 libcxr_world_preview
, libcxr_free_tri_mesh
]
440 def libcxr_log_callback(logStr
):
441 print( F
"{logStr.decode('utf-8')}",end
='' )
443 cxr_line_positions
= None
444 cxr_line_colours
= None
446 def cxr_reset_lines():
447 global cxr_line_positions
, cxr_line_colours
449 cxr_line_positions
= []
450 cxr_line_colours
= []
452 def cxr_batch_lines():
453 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
455 cxr_view_lines
= batch_for_shader(\
456 cxr_view_shader
, 'LINES',\
457 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
459 def libcxr_line_callback( p0
,p1
,colour
):
460 global cxr_line_colours
, cxr_line_positions
462 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
463 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
464 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
465 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
468 # ------------------------------------------------------------------------------
473 NBVTF_IMAGE_FORMAT_ABGR8888
= 1
474 NBVTF_IMAGE_FORMAT_BGR888
= 3
475 NBVTF_IMAGE_FORMAT_DXT1
= 13
476 NBVTF_IMAGE_FORMAT_DXT5
= 15
477 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
478 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
479 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
480 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
481 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
483 libnbvtf_convert
= extern( "nbvtf_convert", \
484 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
487 libnbvtf_init
= extern( "nbvtf_init", [], None )
488 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
491 # --------------------------
494 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
496 # Unload libraries if existing
497 def _reload( lib
, path
):
499 _handle
= lib
._handle
500 for i
in range(10): libc_dlclose( _handle
)
503 return cdll
.LoadLibrary( F
'{os.path.dirname(__file__)}/{path}.so' )
505 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
506 libcxr
= _reload( libcxr
, "libcxr" )
508 for fd
in libnbvtf_funcs
:
509 fd
.loadfrom( libnbvtf
)
512 for fd
in libcxr_funcs
:
513 fd
.loadfrom( libcxr
)
516 global c_libcxr_log_callback
, c_libcxr_line_callback
518 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
519 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
521 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
522 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
523 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
525 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
526 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
528 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
529 print( F
"libcxr build time: {build_time.value}" )
534 # ------------------------------------------------------------------------------
536 # Standard entity functions, think of like base.fgd
538 def cxr_get_origin(context
):
539 return context
['object'].location
* context
['transform']['scale'] + \
540 mathutils
.Vector(context
['transform']['offset'])
542 def cxr_get_angles(context
):
543 obj
= context
['object']
544 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
551 def cxr_baseclass(classes
, other
):
554 base
.update(x
.copy())
557 # EEVEE Light component converter -> Source 1
559 def ent_lights(context
):
560 obj
= context
['object']
561 kvs
= cxr_baseclass([ent_origin
],\
563 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
564 "_lightHDR": '-1 -1 -1 1',
568 light_base
= [(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
569 [obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
]
571 if obj
.data
.type == 'SPOT' or obj
.data
.type == 'SUN':
572 # Blenders directional lights are -z forward
573 # Source is +x, however, it seems to use a completely different system.
574 # Since we dont care about roll for spotlights, we just take the
575 # pitch and yaw via trig
577 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
578 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
579 dir_pitch
= math
.asin(fwd
[2]) * 57.295779513
580 dir_yaw
= math
.atan2(fwd
[1],fwd
[0]) * 57.295779513
582 if obj
.data
.type == 'SPOT':
583 kvs
['_light'] = [ int(x
) for x
in light_base
]
584 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
585 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
587 kvs
['pitch'] = dir_pitch
588 kvs
['angles'] = [ 0, dir_yaw
, 0 ]
589 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
592 # Blender's default has a much more 'nice'
594 kvs
['_linear_attn'] = 1.0
596 elif obj
.data
.type == 'POINT':
597 kvs
['_light'] = [ int(x
) for x
in light_base
]
598 kvs
['_quadratic_attn'] = 1.0
599 kvs
['_linear_attn'] = 0.0
601 elif obj
.data
.type == 'SUN':
602 light_base
[3] *= 300.0 * 5
603 kvs
['_light'] = [ int(x
) for x
in light_base
]
605 ambient
= bpy
.context
.scene
.world
.color
606 kvs
['_ambient'] = [int(pow(ambient
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
608 kvs
['_ambientHDR'] = [-1,-1,-1,1]
609 kvs
['_AmbientScaleHDR'] = 1
610 kvs
['pitch'] = dir_pitch
611 kvs
['angles'] = [ dir_pitch
, dir_yaw
, 0.0 ]
612 kvs
['SunSpreadAngle'] = 0
616 def ent_prop(context
):
617 if isinstance( context
['object'], bpy
.types
.Collection
):
619 target
= context
['object']
620 pos
= mathutils
.Vector(context
['origin'])
621 pos
+= mathutils
.Vector(context
['transform']['offset'])
623 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
624 kvs
['angles'] = [0,180,0]
626 kvs
= cxr_baseclass([ent_origin
],{})
627 target
= context
['object'].instance_collection
629 obj
= context
['object']
630 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
633 angle
[1] = euler
[2] + 180.0 # Dunno...
636 kvs
['angles'] = angle
639 kvs
['enablelightbounce'] = 1
640 kvs
['disableshadows'] = 0
641 kvs
['fademindist'] = -1
643 kvs
['model'] = F
"{asset_path('models',target)}.mdl".lower()
644 kvs
['renderamt'] = 255
645 kvs
['rendercolor'] = [255, 255, 255]
648 kvs
['uniformscale'] = 1.0
652 def ent_sky_camera(context
):
653 settings
= bpy
.context
.scene
.cxr_data
654 scale
= settings
.scale_factor
/ settings
.skybox_scale_factor
657 "origin": [_
for _
in context
['transform']['offset']],
658 "angles": [ 0, 0, 0 ],
659 "fogcolor": [255, 255, 255],
660 "fogcolor2": [255, 255, 255],
665 "HDRColorScale": 1.0,
670 def ent_cubemap(context
):
671 obj
= context
['object']
672 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
674 ent_origin
= { "origin": cxr_get_origin
}
675 ent_angles
= { "angles": cxr_get_angles
}
676 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
678 #include the user config
679 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
681 # Blender state callbacks
682 # ------------------------------------------------------------------------------
685 def cxr_on_load(dummy
):
686 global cxr_view_lines
, cxr_view_mesh
688 cxr_view_lines
= None
692 def cxr_dgraph_update(scene
,dgraph
):
694 print( F
"Hallo {time.time()}" )
696 # Convexer compilation functions
697 # ------------------------------------------------------------------------------
699 # Asset path management
701 def asset_uid(asset
):
702 if isinstance(asset
,str):
705 # Create a unique ID string
707 v
= asset
.cxr_data
.asset_id
716 dig
.append( int( v
% len(base
) ) )
722 if bpy
.context
.scene
.cxr_data
.include_names
:
723 name
+= asset
.name
.replace('.','_')
727 # -> <project_name>/<asset_name>
728 def asset_name(asset
):
729 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
731 # -> <subdir>/<project_name>/<asset_name>
732 def asset_path(subdir
, asset
):
733 return F
"{subdir}/{asset_name(asset_uid(asset))}"
735 # -> <csgo>/<subdir>/<project_name>/<asset_name>
736 def asset_full_path(sdir
,asset
):
737 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
738 F
"{asset_path(sdir,asset_uid(asset))}"
740 # Entity functions / infos
741 # ------------------------
743 def cxr_intrinsic_classname(obj
):
744 if obj
.type == 'LIGHT':
746 'SPOT': "light_spot",
748 'SUN': "light_environment" }[ obj
.data
.type ]
750 elif obj
.type == 'LIGHT_PROBE':
752 elif obj
.type == 'EMPTY':
758 def cxr_custom_class(obj
):
759 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
760 else: custom_class
= obj
.cxr_data
.classname
764 def cxr_classname(obj
):
765 intr
= cxr_intrinsic_classname(obj
)
766 if intr
!= None: return intr
768 custom_class
= cxr_custom_class(obj
)
769 if custom_class
!= 'NONE':
775 # intinsic: (k, False, value)
776 # property: (k, True, value or default)
780 def cxr_entity_keyvalues(context
):
781 classname
= context
['classname']
782 obj
= context
['object']
783 if classname
not in cxr_entities
: return None
787 entdef
= cxr_entities
[classname
]
788 kvs
= entdef
['keyvalues']
790 if callable(kvs
): kvs
= kvs(context
)
797 if isinstance(kv
,dict):
799 value
= obj
[ F
"cxrkv_{k}" ]
804 if isinstance(value
,mathutils
.Vector
):
805 value
= [_
for _
in value
]
807 result
+= [(k
, isprop
, value
)]
811 # Extract material information from shader graph data
813 def material_info(mat
):
815 info
['res'] = (512,512)
816 info
['name'] = 'tools/toolsnodraw'
818 if mat
== None or mat
.use_nodes
== False:
822 if mat
.cxr_data
.shader
== 'Builtin':
823 info
['name'] = mat
.name
827 info
['name'] = asset_name(mat
)
829 # Using the cxr_graph_mapping as a reference, go through the shader
830 # graph and gather all $props from it.
832 def _graph_read( node_def
, node
=None, depth
=0 ):
836 def _variant_apply( val
):
839 if isinstance( val
, str ):
842 for shader_variant
in val
:
843 if shader_variant
[0] == mat
.cxr_data
.shader
:
844 return shader_variant
[1]
848 _graph_read
.extracted
= []
850 for node_idname
in node_def
:
851 for n
in mat
.node_tree
.nodes
:
852 if n
.bl_idname
== node_idname
:
853 node_def
= node_def
[node_idname
]
857 for link
in node_def
:
858 if isinstance( node_def
[link
], dict ):
859 inputt
= node
.inputs
[link
]
860 inputt_def
= node_def
[link
]
864 # look for definitions for the connected node type
865 con
= inputt
.links
[0].from_node
867 for node_idname
in inputt_def
:
868 if con
.bl_idname
== node_idname
:
869 con_def
= inputt_def
[ node_idname
]
870 _graph_read( con_def
, con
, depth
+1 )
872 # No definition found! :(
873 # TODO: Make a warning for this?
876 if "default" in inputt_def
:
877 prop
= _variant_apply( inputt_def
['default'] )
878 info
[prop
] = inputt
.default_value
880 prop
= _variant_apply( node_def
[link
] )
881 info
[prop
] = getattr(node
,link
)
883 _graph_read(cxr_graph_mapping
)
885 if "$basetexture" in info
:
886 export_res
= info
['$basetexture'].cxr_data
.export_res
887 info
['res'] = (export_res
[0], export_res
[1])
891 def vec3_min( a
, b
):
892 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
893 def vec3_max( a
, b
):
894 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
896 def cxr_collection_center(collection
, transform
):
898 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
899 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
901 for obj
in collection
.objects
:
902 if obj
.type == 'MESH':
903 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
905 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
906 bounds_min
= vec3_min( bounds_min
, corner
)
907 bounds_max
= vec3_max( bounds_max
, corner
)
909 center
= (bounds_min
+ bounds_max
) / 2.0
911 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
912 origin
*= transform
['scale']
916 # Prepares Scene into dictionary format
918 def cxr_scene_collect():
919 context
= bpy
.context
921 # Make sure all of our asset types have a unique ID
922 def _uid_prepare(objtype
):
928 if vs
.asset_id
in used_ids
:
931 id_max
= max(id_max
,vs
.asset_id
)
932 used_ids
+=[vs
.asset_id
]
933 for vs
in to_generate
:
936 _uid_prepare(bpy
.data
.materials
)
937 _uid_prepare(bpy
.data
.images
)
938 _uid_prepare(bpy
.data
.collections
)
941 "entities": [], # Everything with a classname
942 "geo": [], # All meshes without a classname
943 "heros": [] # Collections prefixed with mdl_
946 def _collect(collection
,transform
):
949 if collection
.name
.startswith('.'): return
950 if collection
.hide_render
: return
952 if collection
.name
.startswith('mdl_'):
953 sceneinfo
['entities'] += [{
954 "object": collection
,
955 "classname": "prop_static",
956 "transform": transform
,
957 "origin": cxr_collection_center( collection
, transform
)
960 sceneinfo
['heros'] += [{
961 "collection": collection
,
962 "transform": transform
,
963 "origin": cxr_collection_center( collection
, transform
)
967 for obj
in collection
.objects
:
968 if obj
.hide_get(): continue
970 classname
= cxr_classname( obj
)
972 if classname
!= None:
973 sceneinfo
['entities'] += [{
975 "classname": classname
,
976 "transform": transform
978 elif obj
.type == 'MESH':
979 sceneinfo
['geo'] += [{
981 "transform": transform
984 for c
in collection
.children
:
985 _collect( c
, transform
)
988 "scale": context
.scene
.cxr_data
.scale_factor
,
993 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
994 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
997 if 'main' in bpy
.data
.collections
:
998 _collect( bpy
.data
.collections
['main'], transform_main
)
1000 if 'skybox' in bpy
.data
.collections
:
1001 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1003 sceneinfo
['entities'] += [{
1005 "transform": transform_sky
,
1006 "classname": "sky_camera"
1011 # Write VMF out to file (JOB HANDLER)
1013 def cxr_export_vmf(sceneinfo
, output_vmf
):
1016 with
vdf_structure(output_vmf
) as m
:
1017 print( F
"Write: {output_vmf}" )
1019 vmfinfo
= cxr_vmf_context()
1020 vmfinfo
.mapversion
= 4
1022 #TODO: These need to be in options...
1023 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1024 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1025 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1026 vmfinfo
.lightmap_scale
= 12
1028 vmfinfo
.brush_count
= 0
1029 vmfinfo
.entity_count
= 0
1030 vmfinfo
.face_count
= 0
1032 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1034 def _buildsolid( cmd
):
1037 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1039 baked
= mesh_cxr_format( cmd
['object'] )
1040 world
= libcxr_decompose
.call( baked
, None )
1045 vmfinfo
.scale
= cmd
['transform']['scale']
1047 offset
= cmd
['transform']['offset']
1048 vmfinfo
.offset
[0] = offset
[0]
1049 vmfinfo
.offset
[1] = offset
[1]
1050 vmfinfo
.offset
[2] = offset
[2]
1052 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1053 libcxr_free_world
.call( world
)
1058 for brush
in sceneinfo
['geo']:
1059 if not _buildsolid( brush
):
1064 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1067 for ent
in sceneinfo
['entities']:
1069 ctx
= ent
['transform']
1070 cls
= ent
['classname']
1073 m
.kv( 'classname', cls
)
1075 kvs
= cxr_entity_keyvalues( ent
)
1078 if isinstance(kv
[2], list):
1079 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1080 else: m
.kv( kv
[0], str(kv
[2]) )
1084 elif not isinstance( obj
, bpy
.types
.Collection
):
1085 if obj
.type == 'MESH':
1086 if not _buildsolid( ent
):
1096 # COmpile image using NBVTF and hash it (JOB HANDLER)
1098 def compile_image(img
):
1102 name
= asset_name(img
)
1103 src_path
= bpy
.path
.abspath(img
.filepath
)
1105 dims
= img
.cxr_data
.export_res
1107 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1108 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1109 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1110 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1111 }[ img
.cxr_data
.fmt
]
1113 mipmap
= img
.cxr_data
.mipmap
1114 lod
= img
.cxr_data
.lod
1115 clamp
= img
.cxr_data
.clamp
1116 flags
= img
.cxr_data
.flags
1118 q
=bpy
.context
.scene
.cxr_data
.image_quality
1120 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1121 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1122 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1124 if img
.cxr_data
.last_hash
!= comphash
:
1125 print( F
"Texture update: {img.filepath}" )
1127 src
= src_path
.encode('utf-8')
1128 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1132 # texture setting flags
1133 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1135 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1136 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1138 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1139 img
.cxr_data
.last_hash
= comphash
1144 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1147 def compile_material(mat
):
1148 info
= material_info(mat
)
1149 properties
= mat
.cxr_data
1151 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1152 if properties
.shader
== 'Builtin':
1157 # Walk the property tree
1158 def _mlayer( layer
):
1159 nonlocal properties
, props
1162 if isinstance(layer
[decl
],dict): # $property definition
1164 ptype
= pdef
['type']
1170 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1173 # Group expansion (does it have subdefinitions?)
1175 if isinstance(pdef
[ch
],dict):
1184 if ptype
== 'intrinsic':
1188 prop
= getattr(properties
,decl
)
1189 default
= pdef
['default']
1191 if not isinstance(prop
,str) and \
1192 not isinstance(prop
,bpy
.types
.Image
) and \
1193 hasattr(prop
,'__getitem__'):
1194 prop
= tuple([p
for p
in prop
])
1198 props
+= [(decl
,pdef
,prop
)]
1203 if expandview
: _mlayer(pdef
)
1205 _mlayer( cxr_shader_params
)
1208 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1209 vmt
.node( properties
.shader
)
1210 vmt
.put( "// Convexer export\n" )
1219 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1222 if isinstance(prop
,bpy
.types
.Image
):
1223 vmt
.kv( decl
, asset_name(prop
))
1224 elif isinstance(prop
,bool):
1225 vmt
.kv( decl
, '1' if prop
else '0' )
1226 elif isinstance(prop
,str):
1227 vmt
.kv( decl
, prop
)
1228 elif isinstance(prop
,float) or isinstance(prop
,int):
1229 vmt
.kv( decl
, _numeric(prop
) )
1230 elif isinstance(prop
,tuple):
1231 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1233 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1238 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1239 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1241 # Compute hash value
1242 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1244 #for obj in mdl.objects:
1245 # if obj.type != 'MESH':
1248 # ev = obj.evaluated_get(dgraph).data
1249 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1250 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1252 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1253 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1255 # if ev.uv_layers.active != None:
1256 # uv_layer = ev.uv_layers.active.data
1257 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1261 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1262 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1263 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1264 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1265 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1266 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1268 #if chash != mdl.cxr_data.last_hash:
1269 # mdl.cxr_data.last_hash = chash
1270 # print( F"Compile: {mdl.name}" )
1274 bpy
.ops
.object.select_all(action
='DESELECT')
1277 def _get_layer(col
,name
):
1278 for c
in col
.children
:
1281 sub
= _get_layer(c
,name
)
1285 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1287 prev_state
= layer
.hide_viewport
1288 layer
.hide_viewport
=False
1290 # Collect materials to be compiled, and temp rename for export
1294 for obj
in mdl
.objects
:
1295 if obj
.name
== F
"{mdl.name}_phy":
1299 obj
.select_set(state
=True)
1300 for ms
in obj
.material_slots
:
1301 if ms
.material
!= None:
1302 if ms
.material
not in mat_dict
:
1303 mat_dict
[ms
.material
] = ms
.material
.name
1304 ms
.material
.name
= asset_uid(ms
.material
)
1305 ms
.material
.use_nodes
= False
1308 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1309 check_existing
=False,
1311 apply_unit_scale
=False,
1312 bake_space_transform
=False
1315 bpy
.ops
.object.select_all(action
='DESELECT')
1318 vphys
.select_set(state
=True)
1319 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1320 check_existing
=False,
1322 apply_unit_scale
=False,
1323 bake_space_transform
=False
1325 bpy
.ops
.object.select_all(action
='DESELECT')
1327 # Fix material names back to original
1328 for mat
in mat_dict
:
1329 mat
.name
= mat_dict
[mat
]
1330 mat
.use_nodes
= True
1332 layer
.hide_viewport
=prev_state
1335 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1336 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1337 #o.write(F'$scale .32\n')
1338 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1339 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1340 o
.write(F
'$staticprop\n')
1341 o
.write(F
'$origin {origin[0]} {origin[1]} {origin[2]}\n')
1344 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1346 o
.write(" $concave\n")
1349 o
.write(F
'$cdmaterials {project_name}\n')
1350 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1354 # Copy bsp file (and also lightpatch it)
1356 def cxr_patchmap( src
, dst
):
1357 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1358 shutil
.copyfile( src
, dst
)
1361 # Convexer operators
1362 # ------------------------------------------------------------------------------
1364 # Force reload of shared libraries
1366 class CXR_RELOAD(bpy
.types
.Operator
):
1367 bl_idname
="convexer.reload"
1369 def execute(_
,context
):
1373 # Used for exporting data to use with ASAN builds
1375 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1376 bl_idname
="convexer.dev_test"
1377 bl_label
="Export development data"
1379 def execute(_
,context
):
1380 # Prepare input data
1381 mesh_src
= mesh_cxr_format(context
.active_object
)
1382 libcxr_write_test_data
.call( pointer(mesh_src
) )
1385 # UI: Preview how the brushes will looks in 3D view
1387 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1388 bl_idname
="convexer.preview"
1389 bl_label
="Preview Brushes"
1394 def execute(_
,context
):
1397 def modal(_
,context
,event
):
1398 global cxr_view_mesh
1399 static
= _
.__class
__
1401 if event
.type == 'ESC':
1404 cxr_view_mesh
= None
1405 static
.RUNNING
= False
1410 return {'PASS_THROUGH'}
1412 def invoke(_
,context
,event
):
1413 global cxr_view_shader
, cxr_view_mesh
1414 static
= _
.__class
__
1415 static
.LASTERR
= None
1419 mesh_src
= mesh_cxr_format(context
.active_object
)
1422 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1425 cxr_view_mesh
= None
1429 static
.LASTERR
= ["There is no error", \
1435 "Non-Convex Polygon",\
1440 return {'CANCELLED'}
1442 context
.window_manager
.modal_handler_add(_
)
1443 return {'RUNNING_MODAL'}
1445 # Generate preview using cxr
1447 ptrpreview
= libcxr_world_preview
.call( world
)
1448 preview
= ptrpreview
[0]
1450 vertices
= preview
.vertices
[:preview
.vertex_count
]
1451 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1453 colours
= preview
.colours
[:preview
.vertex_count
]
1454 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1456 indices
= preview
.indices
[:preview
.indices_count
]
1457 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1458 for i
in range(int(preview
.indices_count
/3)) ]
1460 cxr_view_mesh
= batch_for_shader(
1461 cxr_view_shader
, 'TRIS',
1462 { "pos": vertices
, "color": colours
},
1466 libcxr_free_tri_mesh
.call( ptrpreview
)
1467 libcxr_free_world
.call( world
)
1471 # Allow user to spam the operator
1473 return {'CANCELLED'}
1475 if not static
.RUNNING
:
1476 static
.RUNNING
= True
1477 context
.window_manager
.modal_handler_add(_
)
1478 return {'RUNNING_MODAL'}
1480 # Search for VMF compiler executables in subdirectory
1482 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1483 bl_idname
="convexer.detect_compilers"
1484 bl_label
="Find compilers"
1486 def execute(self
,context
):
1487 scene
= context
.scene
1488 settings
= scene
.cxr_data
1489 subdir
= settings
.subdir
1491 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1492 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1493 if os
.path
.exists(searchpath
):
1494 settings
[F
'exe_{exename}'] = searchpath
1498 # Main compile function
1500 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1501 bl_idname
="convexer.chain"
1502 bl_label
="Compile Chain"
1517 def cancel(_
,context
):
1518 global cxr_jobs_batch
1519 static
= _
.__class
__
1520 wm
= context
.window_manager
1522 if static
.SUBPROC
!= None:
1523 static
.SUBPROC
.terminate()
1524 static
.SUBPROC
= None
1526 if static
.TIMER
!= None:
1527 wm
.event_timer_remove( static
.TIMER
)
1532 cxr_jobs_batch
= None
1536 def modal(_
,context
,ev
):
1537 static
= _
.__class
__
1539 if ev
.type == 'TIMER':
1540 global cxr_jobs_batch
1542 if static
.WAIT_REDRAW
:
1544 return {'PASS_THROUGH'}
1545 static
.WAIT_REDRAW
= True
1547 if static
.USER_EXIT
:
1548 print( "Chain USER_EXIT" )
1549 return _
.cancel(context
)
1551 if static
.SUBPROC
!= None:
1552 # Deal with async modes
1553 status
= static
.SUBPROC
.poll()
1556 # Cannot redirect STDOUT through here without causing
1557 # undefined behaviour due to the Blender Python specification.
1559 # Have to write it out to a file and read it back in.
1561 with
open("/tmp/convexer_compile_log.txt","r") as log
:
1562 static
.LOG
= log
.readlines()
1563 return {'PASS_THROUGH'}
1565 #for l in static.SUBPROC.stdout:
1566 # print( F'-> {l.decode("utf-8")}',end='' )
1567 static
.SUBPROC
= None
1570 print(F
'Compiler () error: {status}')
1571 return _
.cancel(context
)
1573 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1574 cxr_jobs_update_graph( static
.JOBINFO
)
1576 return {'PASS_THROUGH'}
1578 # Compile syncronous thing
1579 for sys
in static
.JOBINFO
:
1580 for i
,target
in enumerate(sys
['jobs']):
1583 if callable(sys
['exec']):
1584 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1586 if not sys
['exec'](*target
):
1587 print( "Job failed" )
1588 return _
.cancel(context
)
1590 sys
['jobs'][i
] = None
1593 # Run external executable (wine)
1594 static
.SUBPROC
= subprocess
.Popen( target
,
1595 stdout
=static
.FILE
,\
1596 stderr
=subprocess
.PIPE
,\
1601 cxr_jobs_update_graph( static
.JOBINFO
)
1603 return {'PASS_THROUGH'}
1606 print( "All jobs completed!" )
1607 cxr_jobs_batch
= None
1610 return _
.cancel(context
)
1612 return {'PASS_THROUGH'}
1614 def invoke(_
,context
,event
):
1615 static
= _
.__class
__
1616 wm
= context
.window_manager
1618 if static
.TIMER
== None:
1619 print("Launching compiler toolchain")
1621 # Run static compilation units now (collect, vmt..)
1622 filepath
= bpy
.data
.filepath
1623 directory
= os
.path
.dirname(filepath
)
1624 settings
= bpy
.context
.scene
.cxr_data
1626 asset_dir
= F
"{directory}/modelsrc"
1627 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1628 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1629 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1631 os
.makedirs( asset_dir
, exist_ok
=True )
1632 os
.makedirs( material_dir
, exist_ok
=True )
1633 os
.makedirs( model_dir
, exist_ok
=True )
1635 static
.FILE
= open(F
"/tmp/convexer_compile_log.txt","w")
1638 sceneinfo
= cxr_scene_collect()
1644 for brush
in sceneinfo
['geo']:
1645 for ms
in brush
['object'].material_slots
:
1646 a_materials
.add( ms
.material
)
1647 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1648 errmat
= ms
.material
.name
1649 errnam
= brush
['object'].name
1650 print( F
"Vertex shader {errmat} used on {errnam}")
1651 return {'CANCELLED'}
1655 for ent
in sceneinfo
['entities']:
1656 if ent
['object'] == None: continue
1658 if ent
['classname'] == 'prop_static':
1660 if isinstance(obj
,bpy
.types
.Collection
):
1662 a_models
.add( target
)
1663 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
1664 settings
.project_name
, ent
['transform'])]
1666 target
= obj
.instance_collection
1667 if target
in a_models
:
1669 a_models
.add( target
)
1671 # TODO: Should take into account collection instancing offset
1672 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
1673 settings
.project_name
, ent
['transform'])]
1675 elif ent
['object'].type == 'MESH':
1676 for ms
in ent
['object'].material_slots
:
1677 a_materials
.add( ms
.material
)
1679 for mdl
in a_models
:
1680 uid
= asset_uid(mdl
)
1681 qc_jobs
+= [F
'{uid}.qc']
1683 for obj
in mdl
.objects
:
1684 for ms
in obj
.material_slots
:
1685 a_materials
.add( ms
.material
)
1686 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1687 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1689 errmat
= ms
.material
.name
1691 print( F
"Lightmapped shader {errmat} used on {errnam}")
1692 return {'CANCELLED'}
1695 for mat
in a_materials
:
1696 for pair
in compile_material(mat
):
1701 if isinstance(prop
,bpy
.types
.Image
):
1703 if 'flags' in pdef
: flags
= pdef
['flags']
1704 if prop
not in image_jobs
:
1705 image_jobs
+= [(prop
,)]
1706 prop
.cxr_data
.flags
= flags
1712 if settings
.comp_vmf
:
1713 static
.JOBINFO
+= [{
1714 "title": "Convexer",
1716 "colour": (1.0,0.3,0.1,1.0),
1717 "exec": cxr_export_vmf
,
1718 "jobs": [(sceneinfo
,output_vmf
)]
1721 if settings
.comp_textures
:
1722 if len(image_jobs
) > 0:
1723 static
.JOBINFO
+= [{
1724 "title": "Textures",
1726 "colour": (0.1,1.0,0.3,1.0),
1727 "exec": compile_image
,
1731 game
= 'z:'+settings
.subdir
.replace('/','\\')
1733 '-game', game
, settings
.project_name
1737 if settings
.comp_models
:
1738 if len(model_jobs
) > 0:
1739 static
.JOBINFO
+= [{
1742 "colour": (0.5,0.5,1.0,1.0),
1743 "exec": cxr_export_modelsrc
,
1747 if len(qc_jobs
) > 0:
1748 static
.JOBINFO
+= [{
1749 "title": "StudioMDL",
1751 "colour": (0.8,0.1,0.1,1.0),
1752 "exec": "studiomdl",
1753 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1754 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1759 if settings
.comp_compile
:
1760 static
.JOBINFO
+= [{
1763 "colour": (0.1,0.2,1.0,1.0),
1765 "jobs": [[settings
[F
'exe_vbsp']] + args
],
1769 static
.JOBINFO
+= [{
1772 "colour": (0.9,0.5,0.5,1.0),
1774 "jobs": [[settings
[F
'exe_vvis']] + ['-fast'] + args
],
1778 vrad_opt
= settings
.opt_vrad
.split()
1779 static
.JOBINFO
+= [{
1782 "colour": (0.9,0.2,0.3,1.0),
1784 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
1788 static
.JOBINFO
+= [{
1791 "colour": (0.0,1.0,0.4,1.0),
1792 "exec": cxr_patchmap
,
1793 "jobs": [(F
"{directory}/{settings.project_name}.bsp",\
1794 F
"{settings.subdir}/maps/{settings.project_name}.bsp")]
1797 static
.USER_EXIT
=False
1798 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
1799 wm
.modal_handler_add(_
)
1801 cxr_jobs_update_graph( static
.JOBINFO
)
1803 return {'RUNNING_MODAL'}
1805 print("Chain exiting...")
1806 static
.USER_EXIT
=True
1807 return {'RUNNING_MODAL'}
1809 class CXR_RESET_HASHES(bpy
.types
.Operator
):
1810 bl_idname
="convexer.hash_reset"
1811 bl_label
="Reset asset hashes"
1813 def execute(_
,context
):
1814 for c
in bpy
.data
.collections
:
1815 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1816 c
.cxr_data
.asset_id
=0
1818 for t
in bpy
.data
.images
:
1819 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1820 t
.cxr_data
.asset_id
=0
1824 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
1825 bl_idname
="convexer.matcomp"
1826 bl_label
="Recompile Material"
1828 def execute(_
,context
):
1829 active_obj
= bpy
.context
.active_object
1830 active_mat
= active_obj
.active_material
1832 #TODO: reduce code dupe (L1663)
1833 for pair
in compile_material(active_mat
):
1838 if isinstance(prop
,bpy
.types
.Image
):
1840 if 'flags' in pdef
: flags
= pdef
['flags']
1841 prop
.cxr_data
.flags
= flags
1843 compile_image( prop
)
1845 settings
= bpy
.context
.scene
.cxr_data
1846 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
1847 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
1850 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
1851 o
.write('sv_cheats 1\n')
1852 o
.write('mp_warmup_pausetimer 1\n')
1853 o
.write('bot_kick\n')
1854 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
1859 # ------------------------------------------------------------------------------
1861 # Helper buttons for 3d toolbox view
1863 class CXR_VIEW3D( bpy
.types
.Panel
):
1864 bl_idname
= "VIEW3D_PT_convexer"
1865 bl_label
= "Convexer"
1866 bl_space_type
= 'VIEW_3D'
1867 bl_region_type
= 'UI'
1868 bl_category
= "Convexer"
1871 def poll(cls
, context
):
1872 return (context
.object is not None)
1874 def draw(_
, context
):
1878 row
.operator("convexer.preview")
1880 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
1882 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
1884 # Main scene properties interface, where all the settings go
1886 class CXR_INTERFACE(bpy
.types
.Panel
):
1888 bl_idname
="SCENE_PT_convexer"
1889 bl_space_type
='PROPERTIES'
1890 bl_region_type
='WINDOW'
1893 def draw(_
,context
):
1894 _
.layout
.operator("convexer.reload")
1895 _
.layout
.operator("convexer.dev_test")
1896 _
.layout
.operator("convexer.preview")
1897 _
.layout
.operator("convexer.hash_reset")
1899 settings
= context
.scene
.cxr_data
1901 _
.layout
.prop(settings
, "debug")
1902 _
.layout
.prop(settings
, "scale_factor")
1903 _
.layout
.prop(settings
, "skybox_scale_factor")
1904 _
.layout
.prop(settings
, "skyname" )
1905 _
.layout
.prop(settings
, "lightmap_scale")
1906 _
.layout
.prop(settings
, "light_scale" )
1907 _
.layout
.prop(settings
, "image_quality" )
1909 box
= _
.layout
.box()
1911 box
.prop(settings
, "project_name")
1912 box
.prop(settings
, "subdir")
1914 box
= _
.layout
.box()
1915 box
.operator("convexer.detect_compilers")
1916 box
.prop(settings
, "exe_studiomdl")
1917 box
.prop(settings
, "exe_vbsp")
1918 box
.prop(settings
, "exe_vvis")
1919 box
.prop(settings
, "exe_vrad")
1920 box
.prop(settings
, "opt_vrad")
1924 row
.prop(settings
,"comp_vmf")
1925 row
.prop(settings
,"comp_textures")
1926 row
.prop(settings
,"comp_models")
1927 row
.prop(settings
,"comp_compile")
1929 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
1932 row
.operator("convexer.chain", text
=text
)
1935 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1936 bl_label
="VMT Properties"
1937 bl_idname
="SCENE_PT_convexer_vmt"
1938 bl_space_type
='PROPERTIES'
1939 bl_region_type
='WINDOW'
1940 bl_context
="material"
1942 def draw(_
,context
):
1943 active_object
= bpy
.context
.active_object
1944 if active_object
== None: return
1946 active_material
= active_object
.active_material
1947 if active_material
== None: return
1949 properties
= active_material
.cxr_data
1950 info
= material_info( active_material
)
1952 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1953 row
= _
.layout
.row()
1954 row
.prop( properties
, "shader" )
1955 row
.operator( "convexer.matcomp" )
1957 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
1959 def _mtex( name
, img
, uiParent
):
1962 box
= uiParent
.box()
1963 box
.label( text
=F
'{name} "{img.filepath}"' )
1965 if ((x
& (x
- 1)) == 0):
1968 closest_diff
= 10000000
1970 dist
= abs((1 << i
)-x
)
1971 if dist
< closest_diff
:
1976 return 1 << (closest
+1)
1978 return 1 << (closest
-1)
1983 row
.prop( img
.cxr_data
, "export_res" )
1984 row
.prop( img
.cxr_data
, "fmt" )
1987 row
.prop( img
.cxr_data
, "mipmap" )
1988 row
.prop( img
.cxr_data
, "lod" )
1989 row
.prop( img
.cxr_data
, "clamp" )
1991 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1992 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1994 def _mview( layer
, uiParent
):
1998 if isinstance(layer
[decl
],dict): # $property definition
2000 ptype
= pdef
['type']
2006 if ('shaders' in pdef
) and \
2007 (properties
.shader
not in pdef
['shaders']):
2010 if ptype
== 'intrinsic':
2011 if decl
not in info
:
2016 if isinstance(pdef
[ch
],dict):
2017 if ptype
== 'ui' or ptype
== 'intrinsic':
2019 elif getattr(properties
,decl
) == pdef
['default']:
2022 thisnode
= uiParent
.box()
2026 thisnode
.label( text
=decl
)
2027 elif ptype
== 'intrinsic':
2028 if isinstance(info
[decl
], bpy
.types
.Image
):
2029 _mtex( decl
, info
[decl
], thisnode
)
2031 # hidden intrinsic value.
2032 # Means its a float array or something not an image
2033 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2035 thisnode
.prop(properties
,decl
)
2036 if expandview
: _mview(pdef
,thisnode
)
2038 _mview( cxr_shader_params
, _
.layout
)
2040 def cxr_entity_changeclass(_
,context
):
2041 active_object
= context
.active_object
2043 # Create ID properties
2045 classname
= cxr_custom_class(active_object
)
2047 if classname
in cxr_entities
:
2048 entdef
= cxr_entities
[classname
]
2050 kvs
= entdef
['keyvalues']
2051 if callable(kvs
): kvs
= kvs(active_object
)
2057 if callable(kv
) or not isinstance(kv
,dict): continue
2059 if key
not in active_object
:
2060 active_object
[key
] = kv
['default']
2061 id_prop
= active_object
.id_properties_ui(key
)
2062 id_prop
.update(default
=kv
['default'])
2064 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2065 bl_label
="Entity Config"
2066 bl_idname
="SCENE_PT_convexer_entity"
2067 bl_space_type
='PROPERTIES'
2068 bl_region_type
='WINDOW'
2071 def draw(_
,context
):
2072 active_object
= bpy
.context
.active_object
2074 if active_object
== None: return
2077 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2081 ecn
= cxr_intrinsic_classname( active_object
)
2082 classname
= cxr_custom_class( active_object
)
2085 if active_object
.type == 'MESH':
2086 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2087 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2089 if classname
== 'NONE':
2092 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2093 _
.layout
.enabled
=False
2096 kvs
= cxr_entity_keyvalues( {
2097 "object": active_object
,
2098 "transform": default_context
,
2099 "classname": classname
2105 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2107 row
= _
.layout
.row()
2109 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2111 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2113 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2114 bl_label
= "Source Settings"
2115 bl_idname
= "LIGHT_PT_cxr"
2116 bl_space_type
= 'PROPERTIES'
2117 bl_region_type
= 'WINDOW'
2120 def draw(self
, context
):
2121 layout
= self
.layout
2122 scene
= context
.scene
2124 active_object
= bpy
.context
.active_object
2125 if active_object
== None: return
2127 if active_object
.type == 'LIGHT' or \
2128 active_object
.type == 'LIGHT_PROBE':
2130 properties
= active_object
.data
.cxr_data
2132 if active_object
.type == 'LIGHT':
2133 layout
.prop( properties
, "realtime" )
2134 elif active_object
.type == 'LIGHT_PROBE':
2135 layout
.prop( properties
, "size" )
2138 # ------------------------------------------------------------------------------
2140 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2141 export_res
: bpy
.props
.IntVectorProperty(
2143 description
="Texture Export Resolution",
2149 fmt
: bpy
.props
.EnumProperty(
2152 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2153 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2154 ('RGB', "RGB", "Uncompressed", '', 2),
2155 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2157 description
="Image format",
2160 last_hash
: bpy
.props
.StringProperty( name
="" )
2161 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2163 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2164 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2165 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2166 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2168 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2169 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2171 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2172 size
: bpy
.props
.EnumProperty(
2175 ('1',"1x1",'','',0),
2176 ('2',"2x2",'','',1),
2177 ('3',"4x4",'','',2),
2178 ('4',"8x8",'','',3),
2179 ('5',"16x16",'','',4),
2180 ('6',"32x32",'','',5),
2181 ('7',"64x64",'','',6),
2182 ('8',"128x128",'','',7),
2183 ('9',"256x256",'','',8)
2185 description
="Texture resolution",
2188 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2189 entity
: bpy
.props
.BoolProperty(name
="")
2191 enum_pointents
= [('NONE',"None","")]
2192 enum_brushents
= [('NONE',"None","")]
2194 for classname
in cxr_entities
:
2195 entdef
= cxr_entities
[classname
]
2196 if 'allow' in entdef
:
2197 itm
= [(classname
, classname
, "")]
2198 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2199 else: enum_brushents
+= itm
2201 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2202 update
=cxr_entity_changeclass
, default
='NONE' )
2204 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2205 update
=cxr_entity_changeclass
, default
='NONE' )
2207 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2208 last_hash
: bpy
.props
.StringProperty( name
="" )
2209 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2211 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2212 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2213 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
2215 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2216 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2217 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2218 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2219 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2220 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2221 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2222 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2224 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
2225 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2226 default
=32.0,min=1.0)
2227 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2228 default
=1.0,min=0.01)
2229 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2230 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2231 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2232 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2234 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2236 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2237 default
=8, min=0, max=18 )
2239 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2240 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2241 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2242 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2244 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2245 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2246 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2247 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2248 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2249 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2250 CXR_COMPILE_MATERIAL
]
2252 vmt_param_dynamic_class
= None
2255 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2258 bpy
.utils
.register_class(c
)
2260 # Build dynamic VMT properties class defined by cxr_shader_params
2261 annotations_dict
= {}
2263 def _dvmt_propogate(layer
):
2264 nonlocal annotations_dict
2267 if isinstance(layer
[decl
],dict): # $property definition
2271 if pdef
['type'] == 'bool':
2272 prop
= bpy
.props
.BoolProperty(\
2273 name
= pdef
['name'],\
2274 default
= pdef
['default'])
2276 elif pdef
['type'] == 'float':
2277 prop
= bpy
.props
.FloatProperty(\
2278 name
= pdef
['name'],\
2279 default
= pdef
['default'])
2281 elif pdef
['type'] == 'vector':
2282 if 'subtype' in pdef
:
2283 prop
= bpy
.props
.FloatVectorProperty(\
2284 name
= pdef
['name'],\
2285 subtype
= pdef
['subtype'],\
2286 default
= pdef
['default'],\
2287 size
= len(pdef
['default']))
2289 prop
= bpy
.props
.FloatVectorProperty(\
2290 name
= pdef
['name'],\
2291 default
= pdef
['default'],\
2292 size
= len(pdef
['default']))
2294 elif pdef
['type'] == 'string':
2295 prop
= bpy
.props
.StringProperty(\
2296 name
= pdef
['name'],\
2297 default
= pdef
['default'])
2299 elif pdef
['type'] == 'enum':
2300 prop
= bpy
.props
.EnumProperty(\
2301 name
= pdef
['name'],\
2302 items
= pdef
['items'],\
2303 default
= pdef
['default'])
2306 annotations_dict
[decl
] = prop
2308 # Recurse into sub-definitions
2309 _dvmt_propogate(pdef
)
2311 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2314 cxr_shaders
[_
]["name"],\
2315 '') for _
in cxr_shaders
],\
2316 default
= next(iter(cxr_shaders
)))
2318 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2321 _dvmt_propogate( cxr_shader_params
)
2322 vmt_param_dynamic_class
= type(
2324 (bpy
.types
.PropertyGroup
,),{
2325 "__annotations__": annotations_dict
2329 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2332 bpy
.types
.Material
.cxr_data
= \
2333 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2334 bpy
.types
.Image
.cxr_data
= \
2335 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2336 bpy
.types
.Object
.cxr_data
= \
2337 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2338 bpy
.types
.Collection
.cxr_data
= \
2339 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2340 bpy
.types
.Light
.cxr_data
= \
2341 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2342 bpy
.types
.LightProbe
.cxr_data
= \
2343 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2344 bpy
.types
.Scene
.cxr_data
= \
2345 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2347 # CXR Scene settings
2350 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2351 cxr_draw
,(),'WINDOW','POST_VIEW')
2353 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2354 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2356 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2357 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2360 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2362 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2364 bpy
.utils
.unregister_class(c
)
2366 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2367 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2369 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2370 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')