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'] = 1.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_modelsrc_vphys( mdl
):
1241 for obj
in mdl
.objects
:
1242 if obj
.name
== F
"{mdl.name}_phy":
1246 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1247 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1249 # Compute hash value
1250 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1252 #for obj in mdl.objects:
1253 # if obj.type != 'MESH':
1256 # ev = obj.evaluated_get(dgraph).data
1257 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1258 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1260 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1261 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1263 # if ev.uv_layers.active != None:
1264 # uv_layer = ev.uv_layers.active.data
1265 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1269 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1270 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1271 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1272 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1273 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1274 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1276 #if chash != mdl.cxr_data.last_hash:
1277 # mdl.cxr_data.last_hash = chash
1278 # print( F"Compile: {mdl.name}" )
1282 bpy
.ops
.object.select_all(action
='DESELECT')
1285 def _get_layer(col
,name
):
1286 for c
in col
.children
:
1289 sub
= _get_layer(c
,name
)
1293 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1295 prev_state
= layer
.hide_viewport
1296 layer
.hide_viewport
=False
1298 # Collect materials to be compiled, and temp rename for export
1302 for obj
in mdl
.objects
:
1303 if obj
.name
== F
"{mdl.name}_phy":
1307 obj
.select_set(state
=True)
1308 for ms
in obj
.material_slots
:
1309 if ms
.material
!= None:
1310 if ms
.material
not in mat_dict
:
1311 mat_dict
[ms
.material
] = ms
.material
.name
1312 ms
.material
.name
= asset_uid(ms
.material
)
1313 ms
.material
.use_nodes
= False
1316 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1317 check_existing
=False,
1319 apply_unit_scale
=False,
1320 bake_space_transform
=False
1323 bpy
.ops
.object.select_all(action
='DESELECT')
1326 vphys
.select_set(state
=True)
1327 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1328 check_existing
=False,
1330 apply_unit_scale
=False,
1331 bake_space_transform
=False
1333 bpy
.ops
.object.select_all(action
='DESELECT')
1335 # Fix material names back to original
1336 for mat
in mat_dict
:
1337 mat
.name
= mat_dict
[mat
]
1338 mat
.use_nodes
= True
1340 layer
.hide_viewport
=prev_state
1343 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1344 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1345 #o.write(F'$scale .32\n')
1346 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1347 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1348 o
.write(F
'$staticprop\n')
1349 o
.write(F
'$origin {origin[0]} {origin[1]} {origin[2]}\n')
1352 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1354 o
.write(" $concave\n")
1357 o
.write(F
'$cdmaterials {project_name}\n')
1358 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1362 # Copy bsp file (and also lightpatch it)
1364 def cxr_patchmap( src
, dst
):
1365 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1366 shutil
.copyfile( src
, dst
)
1369 # Convexer operators
1370 # ------------------------------------------------------------------------------
1372 # Force reload of shared libraries
1374 class CXR_RELOAD(bpy
.types
.Operator
):
1375 bl_idname
="convexer.reload"
1377 def execute(_
,context
):
1381 # Used for exporting data to use with ASAN builds
1383 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1384 bl_idname
="convexer.dev_test"
1385 bl_label
="Export development data"
1387 def execute(_
,context
):
1388 # Prepare input data
1389 mesh_src
= mesh_cxr_format(context
.active_object
)
1390 libcxr_write_test_data
.call( pointer(mesh_src
) )
1393 # UI: Preview how the brushes will looks in 3D view
1395 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1396 bl_idname
="convexer.preview"
1397 bl_label
="Preview Brushes"
1402 def execute(_
,context
):
1405 def modal(_
,context
,event
):
1406 global cxr_view_mesh
1407 static
= _
.__class
__
1409 if event
.type == 'ESC':
1412 cxr_view_mesh
= None
1413 static
.RUNNING
= False
1418 return {'PASS_THROUGH'}
1420 def invoke(_
,context
,event
):
1421 global cxr_view_shader
, cxr_view_mesh
1422 static
= _
.__class
__
1423 static
.LASTERR
= None
1427 mesh_src
= mesh_cxr_format(context
.active_object
)
1430 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1433 cxr_view_mesh
= None
1437 static
.LASTERR
= ["There is no error", \
1443 "Non-Convex Polygon",\
1448 return {'CANCELLED'}
1450 context
.window_manager
.modal_handler_add(_
)
1451 return {'RUNNING_MODAL'}
1453 # Generate preview using cxr
1455 ptrpreview
= libcxr_world_preview
.call( world
)
1456 preview
= ptrpreview
[0]
1458 vertices
= preview
.vertices
[:preview
.vertex_count
]
1459 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1461 colours
= preview
.colours
[:preview
.vertex_count
]
1462 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1464 indices
= preview
.indices
[:preview
.indices_count
]
1465 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1466 for i
in range(int(preview
.indices_count
/3)) ]
1468 cxr_view_mesh
= batch_for_shader(
1469 cxr_view_shader
, 'TRIS',
1470 { "pos": vertices
, "color": colours
},
1474 libcxr_free_tri_mesh
.call( ptrpreview
)
1475 libcxr_free_world
.call( world
)
1479 # Allow user to spam the operator
1481 return {'CANCELLED'}
1483 if not static
.RUNNING
:
1484 static
.RUNNING
= True
1485 context
.window_manager
.modal_handler_add(_
)
1486 return {'RUNNING_MODAL'}
1488 # Search for VMF compiler executables in subdirectory
1490 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1491 bl_idname
="convexer.detect_compilers"
1492 bl_label
="Find compilers"
1494 def execute(self
,context
):
1495 scene
= context
.scene
1496 settings
= scene
.cxr_data
1497 subdir
= settings
.subdir
1499 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1500 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1501 if os
.path
.exists(searchpath
):
1502 settings
[F
'exe_{exename}'] = searchpath
1506 def cxr_compiler_path( compiler
):
1507 settings
= bpy
.context
.scene
.cxr_data
1508 subdir
= settings
.subdir
1509 path
= os
.path
.normpath(F
'{subdir}/../bin/{compiler}.exe')
1511 if os
.path
.exists( path
): return path
1514 def cxr_winepath( path
):
1515 return 'z:'+path
.replace('/','\\')
1517 # Main compile function
1519 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1520 bl_idname
="convexer.chain"
1521 bl_label
="Compile Chain"
1536 def cancel(_
,context
):
1537 global cxr_jobs_batch
1538 static
= _
.__class
__
1539 wm
= context
.window_manager
1541 if static
.SUBPROC
!= None:
1542 static
.SUBPROC
.terminate()
1543 static
.SUBPROC
= None
1545 if static
.TIMER
!= None:
1546 wm
.event_timer_remove( static
.TIMER
)
1551 cxr_jobs_batch
= None
1555 def modal(_
,context
,ev
):
1556 static
= _
.__class
__
1558 if ev
.type == 'TIMER':
1559 global cxr_jobs_batch
1561 if static
.WAIT_REDRAW
:
1563 return {'PASS_THROUGH'}
1564 static
.WAIT_REDRAW
= True
1566 if static
.USER_EXIT
:
1567 print( "Chain USER_EXIT" )
1568 return _
.cancel(context
)
1570 if static
.SUBPROC
!= None:
1571 # Deal with async modes
1572 status
= static
.SUBPROC
.poll()
1575 # Cannot redirect STDOUT through here without causing
1576 # undefined behaviour due to the Blender Python specification.
1578 # Have to write it out to a file and read it back in.
1580 with
open("/tmp/convexer_compile_log.txt","r") as log
:
1581 static
.LOG
= log
.readlines()
1582 return {'PASS_THROUGH'}
1584 #for l in static.SUBPROC.stdout:
1585 # print( F'-> {l.decode("utf-8")}',end='' )
1586 static
.SUBPROC
= None
1589 print(F
'Compiler () error: {status}')
1590 return _
.cancel(context
)
1592 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1593 cxr_jobs_update_graph( static
.JOBINFO
)
1595 return {'PASS_THROUGH'}
1597 # Compile syncronous thing
1598 for sys
in static
.JOBINFO
:
1599 for i
,target
in enumerate(sys
['jobs']):
1602 if callable(sys
['exec']):
1603 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1605 if not sys
['exec'](*target
):
1606 print( "Job failed" )
1607 return _
.cancel(context
)
1609 sys
['jobs'][i
] = None
1612 # Run external executable (wine)
1613 static
.SUBPROC
= subprocess
.Popen( target
,
1614 stdout
=static
.FILE
,\
1615 stderr
=subprocess
.PIPE
,\
1620 cxr_jobs_update_graph( static
.JOBINFO
)
1622 return {'PASS_THROUGH'}
1625 print( "All jobs completed!" )
1626 cxr_jobs_batch
= None
1629 return _
.cancel(context
)
1631 return {'PASS_THROUGH'}
1633 def invoke(_
,context
,event
):
1634 static
= _
.__class
__
1635 wm
= context
.window_manager
1637 if static
.TIMER
!= None:
1638 print("Chain exiting...")
1639 static
.USER_EXIT
=True
1640 return {'RUNNING_MODAL'}
1642 print("Launching compiler toolchain")
1644 # Run static compilation units now (collect, vmt..)
1645 filepath
= bpy
.data
.filepath
1646 directory
= os
.path
.dirname(filepath
)
1647 settings
= bpy
.context
.scene
.cxr_data
1649 asset_dir
= F
"{directory}/modelsrc"
1650 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1651 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1652 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1654 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
1655 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
1656 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
1657 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
1659 os
.makedirs( asset_dir
, exist_ok
=True )
1660 os
.makedirs( material_dir
, exist_ok
=True )
1661 os
.makedirs( model_dir
, exist_ok
=True )
1663 static
.FILE
= open(F
"/tmp/convexer_compile_log.txt","w")
1666 sceneinfo
= cxr_scene_collect()
1672 for brush
in sceneinfo
['geo']:
1673 for ms
in brush
['object'].material_slots
:
1674 a_materials
.add( ms
.material
)
1675 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1676 errmat
= ms
.material
.name
1677 errnam
= brush
['object'].name
1678 print( F
"Vertex shader {errmat} used on {errnam}")
1679 return {'CANCELLED'}
1683 for ent
in sceneinfo
['entities']:
1684 if ent
['object'] == None: continue
1686 if ent
['classname'] == 'prop_static':
1688 if isinstance(obj
,bpy
.types
.Collection
):
1690 a_models
.add( target
)
1691 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
1692 settings
.project_name
, ent
['transform'])]
1694 target
= obj
.instance_collection
1695 if target
in a_models
:
1697 a_models
.add( target
)
1699 # TODO: Should take into account collection instancing offset
1700 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
1701 settings
.project_name
, ent
['transform'])]
1703 elif ent
['object'].type == 'MESH':
1704 for ms
in ent
['object'].material_slots
:
1705 a_materials
.add( ms
.material
)
1707 for mdl
in a_models
:
1708 uid
= asset_uid(mdl
)
1709 qc_jobs
+= [F
'{uid}.qc']
1711 for obj
in mdl
.objects
:
1712 for ms
in obj
.material_slots
:
1713 a_materials
.add( ms
.material
)
1714 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1715 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1717 errmat
= ms
.material
.name
1719 print( F
"Lightmapped shader {errmat} used on {errnam}")
1720 return {'CANCELLED'}
1723 for mat
in a_materials
:
1724 for pair
in compile_material(mat
):
1729 if isinstance(prop
,bpy
.types
.Image
):
1731 if 'flags' in pdef
: flags
= pdef
['flags']
1732 if prop
not in image_jobs
:
1733 image_jobs
+= [(prop
,)]
1734 prop
.cxr_data
.flags
= flags
1737 with
open( packlist
, "w" ) as fp
:
1739 for mat
in a_materials
:
1740 if mat
.cxr_data
.shader
== 'Builtin': continue
1741 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
1742 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
1744 for img_job
in image_jobs
:
1746 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
1747 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
1749 for mdl
in a_models
:
1750 local
= asset_path('models',mdl
)
1751 winep
= cxr_winepath(asset_full_path('models',mdl
))
1753 fp
.write(F
"{local}.vvd\n")
1754 fp
.write(F
"{winep}.vvd\n")
1755 fp
.write(F
"{local}.dx90.vtx\n")
1756 fp
.write(F
"{winep}.dx90.vtx\n")
1757 fp
.write(F
"{local}.mdl\n")
1758 fp
.write(F
"{winep}.mdl\n")
1759 fp
.write(F
"{local}.vvd\n")
1760 fp
.write(F
"{winep}.vvd\n")
1762 if cxr_modelsrc_vphys(mdl
):
1763 fp
.write(F
"{local}.phy\n")
1764 fp
.write(F
"{winep}.phy\n")
1770 if settings
.comp_vmf
:
1771 static
.JOBINFO
+= [{
1772 "title": "Convexer",
1774 "colour": (1.0,0.3,0.1,1.0),
1775 "exec": cxr_export_vmf
,
1776 "jobs": [(sceneinfo
,output_vmf
)]
1779 if settings
.comp_textures
:
1780 if len(image_jobs
) > 0:
1781 static
.JOBINFO
+= [{
1782 "title": "Textures",
1784 "colour": (0.1,1.0,0.3,1.0),
1785 "exec": compile_image
,
1789 game
= cxr_winepath( settings
.subdir
)
1791 '-game', game
, settings
.project_name
1795 if settings
.comp_models
:
1796 if len(model_jobs
) > 0:
1797 static
.JOBINFO
+= [{
1800 "colour": (0.5,0.5,1.0,1.0),
1801 "exec": cxr_export_modelsrc
,
1805 if len(qc_jobs
) > 0:
1806 static
.JOBINFO
+= [{
1807 "title": "StudioMDL",
1809 "colour": (0.8,0.1,0.1,1.0),
1810 "exec": "studiomdl",
1811 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1812 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1817 if settings
.comp_compile
:
1818 static
.JOBINFO
+= [{
1821 "colour": (0.1,0.2,1.0,1.0),
1823 "jobs": [[settings
[F
'exe_vbsp']] + args
],
1827 static
.JOBINFO
+= [{
1830 "colour": (0.9,0.5,0.5,1.0),
1832 "jobs": [[settings
[F
'exe_vvis']] + ['-fast'] + args
],
1836 vrad_opt
= settings
.opt_vrad
.split()
1837 static
.JOBINFO
+= [{
1840 "colour": (0.9,0.2,0.3,1.0),
1842 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
1846 static
.JOBINFO
+= [{
1849 "colour": (0.0,1.0,0.4,1.0),
1850 "exec": cxr_patchmap
,
1851 "jobs": [(bsp_local
,bsp_remote
)]
1854 if settings
.comp_pack
:
1855 static
.JOBINFO
+= [{
1858 "colour": (0.2,0.2,0.2,1.0),
1860 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
1861 cxr_winepath(bsp_remote
),
1862 cxr_winepath(packlist
),
1863 cxr_winepath(bsp_packed
) ]],
1867 if len(static
.JOBINFO
) == 0:
1868 return {'CANCELLED'}
1870 static
.USER_EXIT
=False
1871 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
1872 wm
.modal_handler_add(_
)
1874 cxr_jobs_update_graph( static
.JOBINFO
)
1876 return {'RUNNING_MODAL'}
1878 class CXR_RESET_HASHES(bpy
.types
.Operator
):
1879 bl_idname
="convexer.hash_reset"
1880 bl_label
="Reset asset hashes"
1882 def execute(_
,context
):
1883 for c
in bpy
.data
.collections
:
1884 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1885 c
.cxr_data
.asset_id
=0
1887 for t
in bpy
.data
.images
:
1888 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1889 t
.cxr_data
.asset_id
=0
1893 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
1894 bl_idname
="convexer.matcomp"
1895 bl_label
="Recompile Material"
1897 def execute(_
,context
):
1898 active_obj
= bpy
.context
.active_object
1899 active_mat
= active_obj
.active_material
1901 #TODO: reduce code dupe (L1663)
1902 for pair
in compile_material(active_mat
):
1907 if isinstance(prop
,bpy
.types
.Image
):
1909 if 'flags' in pdef
: flags
= pdef
['flags']
1910 prop
.cxr_data
.flags
= flags
1912 compile_image( prop
)
1914 settings
= bpy
.context
.scene
.cxr_data
1915 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
1916 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
1919 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
1920 o
.write('sv_cheats 1\n')
1921 o
.write('mp_warmup_pausetimer 1\n')
1922 o
.write('bot_kick\n')
1923 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
1928 # ------------------------------------------------------------------------------
1930 # Helper buttons for 3d toolbox view
1932 class CXR_VIEW3D( bpy
.types
.Panel
):
1933 bl_idname
= "VIEW3D_PT_convexer"
1934 bl_label
= "Convexer"
1935 bl_space_type
= 'VIEW_3D'
1936 bl_region_type
= 'UI'
1937 bl_category
= "Convexer"
1940 def poll(cls
, context
):
1941 return (context
.object is not None)
1943 def draw(_
, context
):
1947 row
.operator("convexer.preview")
1949 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
1951 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
1953 # Main scene properties interface, where all the settings go
1955 class CXR_INTERFACE(bpy
.types
.Panel
):
1957 bl_idname
="SCENE_PT_convexer"
1958 bl_space_type
='PROPERTIES'
1959 bl_region_type
='WINDOW'
1962 def draw(_
,context
):
1963 _
.layout
.operator("convexer.reload")
1964 _
.layout
.operator("convexer.dev_test")
1965 _
.layout
.operator("convexer.preview")
1966 _
.layout
.operator("convexer.hash_reset")
1968 settings
= context
.scene
.cxr_data
1970 _
.layout
.prop(settings
, "debug")
1971 _
.layout
.prop(settings
, "scale_factor")
1972 _
.layout
.prop(settings
, "skybox_scale_factor")
1973 _
.layout
.prop(settings
, "skyname" )
1974 _
.layout
.prop(settings
, "lightmap_scale")
1975 _
.layout
.prop(settings
, "light_scale" )
1976 _
.layout
.prop(settings
, "image_quality" )
1978 box
= _
.layout
.box()
1980 box
.prop(settings
, "project_name")
1981 box
.prop(settings
, "subdir")
1983 box
= _
.layout
.box()
1984 box
.operator("convexer.detect_compilers")
1985 box
.prop(settings
, "exe_studiomdl")
1986 box
.prop(settings
, "exe_vbsp")
1987 box
.prop(settings
, "exe_vvis")
1988 box
.prop(settings
, "exe_vrad")
1989 box
.prop(settings
, "opt_vrad")
1993 row
.prop(settings
,"comp_vmf")
1994 row
.prop(settings
,"comp_textures")
1995 row
.prop(settings
,"comp_models")
1996 row
.prop(settings
,"comp_compile")
1997 row
.prop(settings
,"comp_pack")
1999 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
2002 row
.operator("convexer.chain", text
=text
)
2005 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
2006 bl_label
="VMT Properties"
2007 bl_idname
="SCENE_PT_convexer_vmt"
2008 bl_space_type
='PROPERTIES'
2009 bl_region_type
='WINDOW'
2010 bl_context
="material"
2012 def draw(_
,context
):
2013 active_object
= bpy
.context
.active_object
2014 if active_object
== None: return
2016 active_material
= active_object
.active_material
2017 if active_material
== None: return
2019 properties
= active_material
.cxr_data
2020 info
= material_info( active_material
)
2022 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2023 row
= _
.layout
.row()
2024 row
.prop( properties
, "shader" )
2025 row
.operator( "convexer.matcomp" )
2027 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2029 def _mtex( name
, img
, uiParent
):
2032 box
= uiParent
.box()
2033 box
.label( text
=F
'{name} "{img.filepath}"' )
2035 if ((x
& (x
- 1)) == 0):
2038 closest_diff
= 10000000
2040 dist
= abs((1 << i
)-x
)
2041 if dist
< closest_diff
:
2046 return 1 << (closest
+1)
2048 return 1 << (closest
-1)
2053 row
.prop( img
.cxr_data
, "export_res" )
2054 row
.prop( img
.cxr_data
, "fmt" )
2057 row
.prop( img
.cxr_data
, "mipmap" )
2058 row
.prop( img
.cxr_data
, "lod" )
2059 row
.prop( img
.cxr_data
, "clamp" )
2061 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2062 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2064 def _mview( layer
, uiParent
):
2068 if isinstance(layer
[decl
],dict): # $property definition
2070 ptype
= pdef
['type']
2076 if ('shaders' in pdef
) and \
2077 (properties
.shader
not in pdef
['shaders']):
2080 if ptype
== 'intrinsic':
2081 if decl
not in info
:
2086 if isinstance(pdef
[ch
],dict):
2087 if ptype
== 'ui' or ptype
== 'intrinsic':
2089 elif getattr(properties
,decl
) == pdef
['default']:
2092 thisnode
= uiParent
.box()
2096 thisnode
.label( text
=decl
)
2097 elif ptype
== 'intrinsic':
2098 if isinstance(info
[decl
], bpy
.types
.Image
):
2099 _mtex( decl
, info
[decl
], thisnode
)
2101 # hidden intrinsic value.
2102 # Means its a float array or something not an image
2103 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2105 thisnode
.prop(properties
,decl
)
2106 if expandview
: _mview(pdef
,thisnode
)
2108 _mview( cxr_shader_params
, _
.layout
)
2110 def cxr_entity_changeclass(_
,context
):
2111 active_object
= context
.active_object
2113 # Create ID properties
2115 classname
= cxr_custom_class(active_object
)
2117 if classname
in cxr_entities
:
2118 entdef
= cxr_entities
[classname
]
2120 kvs
= entdef
['keyvalues']
2121 if callable(kvs
): kvs
= kvs(active_object
)
2127 if callable(kv
) or not isinstance(kv
,dict): continue
2129 if key
not in active_object
:
2130 active_object
[key
] = kv
['default']
2131 id_prop
= active_object
.id_properties_ui(key
)
2132 id_prop
.update(default
=kv
['default'])
2134 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2135 bl_label
="Entity Config"
2136 bl_idname
="SCENE_PT_convexer_entity"
2137 bl_space_type
='PROPERTIES'
2138 bl_region_type
='WINDOW'
2141 def draw(_
,context
):
2142 active_object
= bpy
.context
.active_object
2144 if active_object
== None: return
2147 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2151 ecn
= cxr_intrinsic_classname( active_object
)
2152 classname
= cxr_custom_class( active_object
)
2155 if active_object
.type == 'MESH':
2156 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2157 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2159 if classname
== 'NONE':
2162 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2163 _
.layout
.enabled
=False
2166 kvs
= cxr_entity_keyvalues( {
2167 "object": active_object
,
2168 "transform": default_context
,
2169 "classname": classname
2175 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2177 row
= _
.layout
.row()
2179 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2181 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2183 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2184 bl_label
= "Source Settings"
2185 bl_idname
= "LIGHT_PT_cxr"
2186 bl_space_type
= 'PROPERTIES'
2187 bl_region_type
= 'WINDOW'
2190 def draw(self
, context
):
2191 layout
= self
.layout
2192 scene
= context
.scene
2194 active_object
= bpy
.context
.active_object
2195 if active_object
== None: return
2197 if active_object
.type == 'LIGHT' or \
2198 active_object
.type == 'LIGHT_PROBE':
2200 properties
= active_object
.data
.cxr_data
2202 if active_object
.type == 'LIGHT':
2203 layout
.prop( properties
, "realtime" )
2204 elif active_object
.type == 'LIGHT_PROBE':
2205 layout
.prop( properties
, "size" )
2208 # ------------------------------------------------------------------------------
2210 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2211 export_res
: bpy
.props
.IntVectorProperty(
2213 description
="Texture Export Resolution",
2219 fmt
: bpy
.props
.EnumProperty(
2222 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2223 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2224 ('RGB', "RGB", "Uncompressed", '', 2),
2225 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2227 description
="Image format",
2230 last_hash
: bpy
.props
.StringProperty( name
="" )
2231 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2233 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2234 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2235 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2236 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2238 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2239 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2241 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2242 size
: bpy
.props
.EnumProperty(
2245 ('1',"1x1",'','',0),
2246 ('2',"2x2",'','',1),
2247 ('3',"4x4",'','',2),
2248 ('4',"8x8",'','',3),
2249 ('5',"16x16",'','',4),
2250 ('6',"32x32",'','',5),
2251 ('7',"64x64",'','',6),
2252 ('8',"128x128",'','',7),
2253 ('9',"256x256",'','',8)
2255 description
="Texture resolution",
2258 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2259 entity
: bpy
.props
.BoolProperty(name
="")
2261 enum_pointents
= [('NONE',"None","")]
2262 enum_brushents
= [('NONE',"None","")]
2264 for classname
in cxr_entities
:
2265 entdef
= cxr_entities
[classname
]
2266 if 'allow' in entdef
:
2267 itm
= [(classname
, classname
, "")]
2268 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2269 else: enum_brushents
+= itm
2271 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2272 update
=cxr_entity_changeclass
, default
='NONE' )
2274 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2275 update
=cxr_entity_changeclass
, default
='NONE' )
2277 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2278 last_hash
: bpy
.props
.StringProperty( name
="" )
2279 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2281 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2282 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2283 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
2285 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2286 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2287 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2288 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2289 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2290 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2291 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2292 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2294 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
2295 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2296 default
=32.0,min=1.0)
2297 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2298 default
=1.0,min=0.01)
2299 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2300 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2301 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2302 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2304 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2306 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2307 default
=8, min=0, max=18 )
2309 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2310 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2311 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2312 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2313 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2315 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2316 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2317 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2318 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2319 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2320 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2321 CXR_COMPILE_MATERIAL
]
2323 vmt_param_dynamic_class
= None
2326 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2329 bpy
.utils
.register_class(c
)
2331 # Build dynamic VMT properties class defined by cxr_shader_params
2332 annotations_dict
= {}
2334 def _dvmt_propogate(layer
):
2335 nonlocal annotations_dict
2338 if isinstance(layer
[decl
],dict): # $property definition
2342 if pdef
['type'] == 'bool':
2343 prop
= bpy
.props
.BoolProperty(\
2344 name
= pdef
['name'],\
2345 default
= pdef
['default'])
2347 elif pdef
['type'] == 'float':
2348 prop
= bpy
.props
.FloatProperty(\
2349 name
= pdef
['name'],\
2350 default
= pdef
['default'])
2352 elif pdef
['type'] == 'vector':
2353 if 'subtype' in pdef
:
2354 prop
= bpy
.props
.FloatVectorProperty(\
2355 name
= pdef
['name'],\
2356 subtype
= pdef
['subtype'],\
2357 default
= pdef
['default'],\
2358 size
= len(pdef
['default']))
2360 prop
= bpy
.props
.FloatVectorProperty(\
2361 name
= pdef
['name'],\
2362 default
= pdef
['default'],\
2363 size
= len(pdef
['default']))
2365 elif pdef
['type'] == 'string':
2366 prop
= bpy
.props
.StringProperty(\
2367 name
= pdef
['name'],\
2368 default
= pdef
['default'])
2370 elif pdef
['type'] == 'enum':
2371 prop
= bpy
.props
.EnumProperty(\
2372 name
= pdef
['name'],\
2373 items
= pdef
['items'],\
2374 default
= pdef
['default'])
2377 annotations_dict
[decl
] = prop
2379 # Recurse into sub-definitions
2380 _dvmt_propogate(pdef
)
2382 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2385 cxr_shaders
[_
]["name"],\
2386 '') for _
in cxr_shaders
],\
2387 default
= next(iter(cxr_shaders
)))
2389 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2392 _dvmt_propogate( cxr_shader_params
)
2393 vmt_param_dynamic_class
= type(
2395 (bpy
.types
.PropertyGroup
,),{
2396 "__annotations__": annotations_dict
2400 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2403 bpy
.types
.Material
.cxr_data
= \
2404 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2405 bpy
.types
.Image
.cxr_data
= \
2406 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2407 bpy
.types
.Object
.cxr_data
= \
2408 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2409 bpy
.types
.Collection
.cxr_data
= \
2410 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2411 bpy
.types
.Light
.cxr_data
= \
2412 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2413 bpy
.types
.LightProbe
.cxr_data
= \
2414 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2415 bpy
.types
.Scene
.cxr_data
= \
2416 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2418 # CXR Scene settings
2421 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2422 cxr_draw
,(),'WINDOW','POST_VIEW')
2424 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2425 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2427 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2428 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2431 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2433 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2435 bpy
.utils
.unregister_class(c
)
2437 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2438 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2440 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2441 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')