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
20 from gpu_extras
.batch
import batch_for_shader
21 from bpy
.app
.handlers
import persistent
24 vmt_param_dynamic_class
= None
26 # libcxr interface (TODO: We can probably automate this)
27 # ======================================================
30 def __init__(_
,name
,argtypes
,restype
):
37 _
.call
= getattr(so
,_
.name
)
38 _
.call
.argtypes
= _
.argtypes
41 _
.call
.restype
= _
.restype
44 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
45 libc_dlclose
.argtypes
= [c_void_p
]
47 # Callback ctypes wrapper...
49 c_libcxr_log_callback
= None
50 c_libcxr_line_callback
= None
52 # Structure definitions
53 class cxr_edge(Structure
):
54 _fields_
= [("i0",c_int32
),
56 ("freestyle",c_int32
)]
58 class cxr_static_loop(Structure
):
59 _fields_
= [("index",c_int32
),
60 ("edge_index",c_int32
),
63 class cxr_polygon(Structure
):
64 _fields_
= [("loop_start",c_int32
),
65 ("loop_total",c_int32
),
66 ("normal",c_double
* 3),
67 ("center",c_double
* 3),
68 ("material_id",c_int32
)]
70 class cxr_material(Structure
):
71 _fields_
= [("res",c_int32
* 2),
74 class cxr_static_mesh(Structure
):
75 _fields_
= [("vertices",POINTER(c_double
* 3)),
76 ("edges",POINTER(cxr_edge
)),
77 ("loops",POINTER(cxr_static_loop
)),
78 ("polys",POINTER(cxr_polygon
)),
79 ("materials",POINTER(cxr_material
)),
81 ("poly_count",c_int32
),
82 ("vertex_count",c_int32
),
83 ("edge_count",c_int32
),
84 ("loop_count",c_int32
),
85 ("material_count",c_int32
)]
87 class cxr_tri_mesh(Structure
):
88 _fields_
= [("vertices",POINTER(c_double
*3)),
89 ("colours",POINTER(c_double
*4)),
90 ("indices",POINTER(c_int32
)),
91 ("indices_count",c_int32
),
92 ("vertex_count",c_int32
)]
94 class cxr_vmf_context(Structure
):
95 _fields_
= [("mapversion",c_int32
),
97 ("detailvbsp",c_char_p
),
98 ("detailmaterial",c_char_p
),
100 ("offset",c_double
*3),
101 ("lightmap_scale",c_int32
),
102 ("brush_count",c_int32
),
103 ("entity_count",c_int32
),
104 ("face_count",c_int32
)]
107 libcxr_decompose
= extern( "cxr_decompose", \
108 [POINTER(cxr_static_mesh
), POINTER(c_int32
)], c_void_p
)
110 libcxr_free_world
= extern( "cxr_free_world", [c_void_p
], None )
111 libcxr_write_test_data
= extern( "cxr_write_test_data", \
112 [POINTER(cxr_static_mesh
)], None )
113 libcxr_world_preview
= extern( "cxr_world_preview", [c_void_p
], \
114 POINTER(cxr_tri_mesh
))
115 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh", [c_void_p
], None )
118 libcxr_begin_vmf
= extern( "cxr_begin_vmf", \
119 [POINTER(cxr_vmf_context
), c_void_p
], None )
121 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities", \
122 [POINTER(cxr_vmf_context
), c_void_p
], None )
124 libcxr_push_world_vmf
= extern("cxr_push_world_vmf", \
125 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
], None )
127 libcxr_end_vmf
= extern( "cxr_end_vmf", \
128 [POINTER(cxr_vmf_context
),c_void_p
], None )
131 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
132 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
133 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
134 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
135 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
136 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
139 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
141 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
142 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
143 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
144 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
145 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
146 libcxr_world_preview
, libcxr_free_tri_mesh
]
152 libnbvtf_convert
= extern( "nbvtf_convert", \
153 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
],
156 libnbvtf_funcs
= [ libnbvtf_convert
]
161 NBVTF_IMAGE_FORMAT_RGBA8888
= 0
162 NBVTF_IMAGE_FORMAT_RGB888
= 2
163 NBVTF_IMAGE_FORMAT_DXT1
= 13
164 NBVTF_IMAGE_FORMAT_DXT5
= 15
165 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
166 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
167 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
168 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
169 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
171 # Wrapper for vdf functions to allow: with o = vdf_structure ...
172 class vdf_structure():
173 def __init__(_
,path
):
176 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
178 print( F
"Could not open file {_.path}" )
181 def __exit__(_
,type,value
,traceback
):
183 libcxr_vdf_close
.call(_
.fp
)
186 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
188 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
190 libcxr_vdf_edon
.call(_
.fp
)
192 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
194 class cxr_object_context():
195 def __init__(_
,scale
,offset_z
):
199 debug_gpu_lines
= None
200 debug_gpu_mesh
= None
201 debug_gpu_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
202 debug_draw_handler
= None
205 def libcxr_log_callback(logStr
):
206 print( F
"{logStr.decode('utf-8')}",end
='' )
208 debug_lines_positions
= None
209 debug_lines_colours
= None
211 def libcxr_reset_debug_lines():
212 global debug_lines_positions
213 global debug_lines_colours
215 debug_lines_positions
= []
216 debug_lines_colours
= []
218 def libcxr_batch_debug_lines():
219 global debug_lines_positions
220 global debug_lines_colours
221 global debug_gpu_lines
222 global debug_gpu_shader
224 debug_gpu_lines
= batch_for_shader(\
225 debug_gpu_shader
, 'LINES',\
226 { "pos": debug_lines_positions
, "color": debug_lines_colours
})
229 def cxr_on_load(dummy
):
230 libcxr_reset_debug_lines()
231 libcxr_batch_debug_lines()
234 def cxr_dgraph_update(scene
,dgraph
):
236 print( F
"Hallo {time.time()}" )
238 def libcxr_line_callback(p0
,p1
,colour
):
239 global debug_lines_positions
240 global debug_lines_colours
241 debug_lines_positions
+= [(p0
[0],p0
[1],p0
[2])]
242 debug_lines_positions
+= [(p1
[0],p1
[1],p1
[2])]
243 debug_lines_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
244 debug_lines_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
247 global debug_gpu_lines
248 global debug_gpu_shader
249 global debug_gpu_mesh
251 debug_gpu_shader
.bind()
253 gpu
.state
.depth_mask_set(False)
254 gpu
.state
.line_width_set(1.5)
255 gpu
.state
.face_culling_set('BACK')
257 gpu
.state
.depth_test_set('NONE')
258 gpu
.state
.blend_set('ALPHA')
259 if debug_gpu_lines
!= None:
260 debug_gpu_lines
.draw(debug_gpu_shader
)
262 gpu
.state
.depth_test_set('LESS_EQUAL')
263 gpu
.state
.blend_set('ADDITIVE')
264 if debug_gpu_mesh
!= None:
265 debug_gpu_mesh
.draw(debug_gpu_shader
)
267 class CXR_RELOAD(bpy
.types
.Operator
):
268 bl_idname
="convexer.reload"
269 bl_label
="Reload convexer"
271 def execute(_
,context
):
272 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
275 libnbvtf
= cdll
.LoadLibrary( os
.path
.dirname(__file__
)+'/libnbvtf.so')
278 _handle
= libcxr
._handle
280 for i
in range(10): libc_dlclose( _handle
)
284 libcxr
= cdll
.LoadLibrary( os
.path
.dirname(__file__
)+'/libcxr.so')
286 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
287 print( F
"libcxr build time: {build_time.value}" )
289 for fd
in libnbvtf_funcs
:
290 fd
.loadfrom( libnbvtf
)
292 for fd
in libcxr_funcs
:
293 fd
.loadfrom( libcxr
)
296 global c_libcxr_log_callback
, c_libcxr_line_callback
298 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
299 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
300 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
302 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
303 POINTER(c_double
),POINTER(c_double
),POINTER(c_double
))
304 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
305 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
313 bpy
.ops
.convexer
.reload()
321 dig
.append( int( v
% 5 ) )
324 ret
+= [ 'a','e','i','o','u' ][d
]
327 def asset_uid(asset
):
328 if isinstance(asset
,str):
330 name
= to_aeiou(asset
.cxr_data
.asset_id
)
331 if bpy
.context
.scene
.cxr_data
.include_names
:
332 name
+= asset
.name
.replace('.','_')
335 # -> <project_name>/<asset_name>
336 def asset_name(asset
):
337 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
339 # -> <subdir>/<project_name>/<asset_name>
340 def asset_path(subdir
, asset
):
341 return F
"{subdir}/{asset_name(asset_uid(asset))}"
343 # -> <csgo>/<subdir>/<project_name>/<asset_name>
344 def asset_full_path(sdir
,asset
):
345 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
346 F
"{asset_path(sdir,asset_uid(asset))}"
348 # view_layer.update() doesnt seem to work,
349 # tag_redraw() seems to have broken
350 # therefore, change a property
352 ob
= bpy
.context
.scene
.objects
[0]
353 ob
.hide_render
= ob
.hide_render
355 # the 'real' way to refresh the scene
356 #for area in bpy.context.window.screen.areas:
357 # if area.type == 'view_3d':
360 # The default shader is the first entry
363 "LightMappedGeneric":
365 "name": "Light Mapped",
370 "name": "Vertex Lit",
385 def material_tex_image(v
):
387 "ShaderNodeTexImage":
393 cxr_graph_mapping
= {
394 "ShaderNodeBsdfPrincipled":
400 "Color1": material_tex_image("basetexture"),
401 "Color2": material_tex_image("decaltexture")
403 "ShaderNodeTexImage":
405 "image":"$basetexture"
408 [("VertexLitGeneric","$color2"),\
409 ("UnlitGeneric","$color2"),\
410 ("LightMappedGeneric","$color")]
414 "ShaderNodeNormalMap":
416 "Color": material_tex_image("bumpmap")
422 cxr_shader_params
= {
426 "shaders": ("UnlitGeneric","VertexLitGeneric","LightMappedGeneric"),
430 "name": "Base Texture",
436 "name": "Decal Texture",
442 "name": "Blend Mode",
445 ('0',"AlphaOver","Default",'',0),
446 ('1',"Multiply","",'',1),
447 ('2',"Modulate","",'',2),
448 ('3',"Additive","",'',3)
456 "name": "Normal Map",
458 "flags": NBVTF_TEXTUREFLAGS_NORMAL
,
479 "shaders": ("VertexLitGeneric", "LightMappedGeneric"),
499 "$phongfresnelranges":
501 "name": "Fresnel Ranges",
503 "default":(1.0,1.0,1.0)
517 "default": (1.0,1.0,1.0)
521 "name": "Light Scale",
525 "$envmaplightscaleminmax":
536 "shaders": ("UnlitGeneric","VertexLitGeneric","LightMappedGeneric"),
540 "name": "Translucent",
546 "name": "Alpha Test",
550 "$alphatestreference":
566 def ent_get_origin(obj
,context
):
567 return obj
.location
* context
.scale
569 def ent_get_angles(obj
,context
):
570 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
577 def ent_baseclass(classes
, other
):
580 base
.update(x
.copy())
583 ent_origin
= { "origin": ent_get_origin
}
584 ent_angles
= { "angles": ent_get_angles
}
585 ent_transform
= ent_baseclass( [ent_origin
], ent_angles
)
587 def ent_lights(obj
,context
):
588 kvs
= ent_baseclass([ent_origin
],\
590 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
591 "_light": [int(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
592 [int(obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
)],
593 "_lightHDR": '-1 -1 -1 1',
597 if obj
.data
.type == 'SPOT':
598 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
599 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
601 # Blenders spotlights are -z forward
602 # Source is +x, however, it seems to use a completely different system.
603 # Since we dont care about roll for spotlights, we just take the
604 # pitch and yaw via trig
606 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
607 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
609 kvs
['pitch'] = math
.asin(fwd
[2]) * 57.295779513
610 kvs
['angles'] = [ 0.0, math
.atan2(fwd
[1],fwd
[0]) * 57.295779513, 0.0 ]
611 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
614 # Blender's default has a much more 'nice'
616 kvs
['_linear_attn'] = 1.0
618 elif obj
.data
.type == 'POINT':
619 kvs
['_quadratic_attn'] = 1.0
620 kvs
['_linear_attn'] = 0.0
622 elif obj
.data
.type == 'SUN':
627 def ent_cubemap(obj
,context
):
628 return ent_baseclass([ent_origin
],\
629 {"cubemapsize": obj
.data
.cxr_data
.size
})
632 "info_player_counterterrorist":
636 "keyvalues": ent_baseclass([ent_transform
],\
638 "priority": {"type": "int", "default": 0 },
639 "enabled": {"type": "int", "default": 1 },
642 "info_player_terrorist":
646 "keyvalues": ent_baseclass([ent_transform
],\
648 "priority": {"type": "int", "default": 0 },
649 "enabled": {"type": "int", "default": 1 },
652 "light": { "keyvalues": ent_lights
},
653 "light_spot": { "keyvalues": ent_lights
},
655 "env_cubemap": { "keyvalues": ent_cubemap
},
663 "TeamNum": {"type": "int", "default": 0 }
668 def cxr_intrinsic_classname(obj
):
669 if obj
.type == 'LIGHT':
671 'SPOT': "light_spot",
673 'SUN': "light_directional" }[ obj
.data
.type ]
675 elif obj
.type == 'LIGHT_PROBE':
677 elif obj
.type == 'EMPTY':
683 def cxr_custom_class(obj
):
684 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
685 else: custom_class
= obj
.cxr_data
.classname
689 def cxr_classname(obj
):
690 intr
= cxr_intrinsic_classname(obj
)
691 if intr
!= None: return intr
693 custom_class
= cxr_custom_class(obj
)
694 if custom_class
!= 'NONE':
700 # intinsic: (k, False, value)
701 # property: (k, True, value or default)
705 def cxr_entity_keyvalues(obj
,context
,classname
):
706 if classname
not in cxr_entities
: return None
710 entdef
= cxr_entities
[classname
]
711 kvs
= entdef
['keyvalues']
713 if callable(kvs
): kvs
= kvs(obj
, context
)
720 if isinstance(kv
,dict):
722 value
= obj
[ F
"cxrkv_{k}" ]
725 value
= kv(obj
,context
)
727 if isinstance(value
,mathutils
.Vector
):
728 value
= [_
for _
in value
]
730 result
+= [(k
, isprop
, value
)]
734 def material_info(mat
):
736 info
['res'] = (512,512)
737 info
['name'] = 'tools/toolsnodraw'
739 if mat
== None or mat
.use_nodes
== False:
743 if mat
.cxr_data
.shader
== 'Builtin':
744 info
['name'] = mat
.name
747 if not hasattr(material_info
,'references'):
748 material_info
.references
= set()
751 material_info
.references
.add(mat
)
752 info
['name'] = asset_name(mat
)
754 # Using the cxr_graph_mapping as a reference, go through the shader
755 # graph and gather all $props from it.
757 def _graph_read( node_def
, node
=None, depth
=0 ):
761 def _variant_apply( val
):
764 if isinstance( val
, str ):
767 for shader_variant
in val
:
768 if shader_variant
[0] == mat
.cxr_data
.shader
:
769 return shader_variant
[1]
773 _graph_read
.extracted
= []
775 for node_idname
in node_def
:
776 for n
in mat
.node_tree
.nodes
:
777 if n
.bl_idname
== node_idname
:
778 node_def
= node_def
[node_idname
]
782 for link
in node_def
:
783 if isinstance( node_def
[link
], dict ):
784 inputt
= node
.inputs
[link
]
785 inputt_def
= node_def
[link
]
789 # look for definitions for the connected node type
790 con
= inputt
.links
[0].from_node
792 for node_idname
in inputt_def
:
793 if con
.bl_idname
== node_idname
:
794 con_def
= inputt_def
[ node_idname
]
795 _graph_read( con_def
, con
, depth
+1 )
797 # No definition found! :(
798 # TODO: Make a warning for this?
801 if "default" in inputt_def
:
802 prop
= _variant_apply( inputt_def
['default'] )
803 info
[prop
] = inputt
.default_value
805 prop
= _variant_apply( node_def
[link
] )
806 info
[prop
] = getattr(node
,link
)
808 _graph_read(cxr_graph_mapping
)
810 if "$basetexture" in info
:
811 export_res
= info
['$basetexture'].cxr_data
.export_res
812 info
['res'] = (export_res
[0], export_res
[1])
816 def mesh_cxr_format(obj
):
817 orig_state
= obj
.mode
818 if orig_state
!= 'OBJECT':
819 bpy
.ops
.object.mode_set(mode
='OBJECT')
821 dgraph
= bpy
.context
.evaluated_depsgraph_get()
822 data
= obj
.evaluated_get(dgraph
).data
824 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
826 mesh
= cxr_static_mesh()
828 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
829 for i
, vert
in enumerate(data
.vertices
):
830 v
= obj
.matrix_world
@ vert
.co
831 vertex_data
[i
][0] = c_double(v
[0])
832 vertex_data
[i
][1] = c_double(v
[1])
833 vertex_data
[i
][2] = c_double(v
[2])
835 loop_data
= (cxr_static_loop
*len(data
.loops
))()
836 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
838 for i
, poly
in enumerate(data
.polygons
):
839 loop_start
= poly
.loop_start
840 loop_end
= poly
.loop_start
+ poly
.loop_total
841 for loop_index
in range(loop_start
, loop_end
):
842 loop
= data
.loops
[loop_index
]
843 loop_data
[loop_index
].index
= loop
.vertex_index
844 loop_data
[loop_index
].edge_index
= loop
.edge_index
847 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
848 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
849 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
851 loop_data
[loop_index
].uv
[0] = c_double(0.0)
852 loop_data
[loop_index
].uv
[1] = c_double(0.0)
853 center
= obj
.matrix_world
@ poly
.center
854 normal
= mtx_rot
@ poly
.normal
856 polygon_data
[i
].loop_start
= poly
.loop_start
857 polygon_data
[i
].loop_total
= poly
.loop_total
858 polygon_data
[i
].normal
[0] = normal
[0]
859 polygon_data
[i
].normal
[1] = normal
[1]
860 polygon_data
[i
].normal
[2] = normal
[2]
861 polygon_data
[i
].center
[0] = center
[0]
862 polygon_data
[i
].center
[1] = center
[1]
863 polygon_data
[i
].center
[2] = center
[2]
864 polygon_data
[i
].material_id
= poly
.material_index
866 edge_data
= (cxr_edge
*len(data
.edges
))()
868 for i
, edge
in enumerate(data
.edges
):
869 edge_data
[i
].i0
= edge
.vertices
[0]
870 edge_data
[i
].i1
= edge
.vertices
[1]
871 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
873 material_data
= (cxr_material
*len(obj
.material_slots
))()
875 for i
, ms
in enumerate(obj
.material_slots
):
876 inf
= material_info(ms
.material
)
877 material_data
[i
].res
[0] = inf
['res'][0]
878 material_data
[i
].res
[1] = inf
['res'][1]
879 material_data
[i
].vmt_path
= inf
['name'].encode('utf-8')
881 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
882 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
883 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
884 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
885 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
887 mesh
.poly_count
= len(data
.polygons
)
888 mesh
.vertex_count
= len(data
.vertices
)
889 mesh
.edge_count
= len(data
.edges
)
890 mesh
.loop_count
= len(data
.loops
)
891 mesh
.material_count
= len(obj
.material_slots
)
893 bpy
.ops
.object.mode_set(mode
=orig_state
)
896 class CXR_WRITE_VMF(bpy
.types
.Operator
):
897 bl_idname
="convexer.write_vmf"
900 def execute(_
,context
):
902 libcxr_reset_debug_lines()
904 # Setup output and state
905 filepath
= bpy
.data
.filepath
906 directory
= os
.path
.dirname(filepath
)
907 settings
= context
.scene
.cxr_data
909 asset_dir
= F
"{directory}/bin"
910 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
911 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
913 os
.makedirs( asset_dir
, exist_ok
=True )
914 os
.makedirs( material_dir
, exist_ok
=True )
915 os
.makedirs( model_dir
, exist_ok
=True )
918 libcxr_reset_debug_lines()
919 material_info
.references
= set()
920 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
922 with
vdf_structure(output_vmf
) as m
:
923 print( F
"Write: {output_vmf}" )
925 vmfinfo
= cxr_vmf_context()
926 vmfinfo
.mapversion
= 4
927 vmfinfo
.skyname
= b
"sky_csgo_night02b"
928 vmfinfo
.detailvbsp
= b
"detail.vbsp"
929 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
930 vmfinfo
.lightmap_scale
= 12
931 vmfinfo
.brush_count
= 0
932 vmfinfo
.entity_count
= 0
933 vmfinfo
.face_count
= 0
935 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
937 # Make sure all of our asset types have a unique ID
938 def _uid_prepare(objtype
):
944 if vs
.asset_id
in used_ids
:
947 id_max
= max(id_max
,vs
.asset_id
)
948 used_ids
+=[vs
.asset_id
]
949 for vs
in to_generate
:
953 _uid_prepare(bpy
.data
.materials
)
954 _uid_prepare(bpy
.data
.images
)
955 _uid_prepare(bpy
.data
.collections
)
957 # Export Brushes and displacement
958 def _collect(collection
,transform
):
959 if collection
.name
.startswith('.'):
962 if collection
.hide_render
:
965 if collection
.name
.startswith('mdl_'):
966 _collect
.heros
+= [(collection
,transform
)]
969 for obj
in collection
.objects
:
970 if obj
.hide_get(): continue
972 classname
= cxr_classname( obj
)
974 if classname
!= None:
975 _collect
.entities
+= [( obj
,transform
,classname
)]
976 elif obj
.type == 'MESH':
977 _collect
.geo
+= [(obj
,transform
)]
979 for c
in collection
.children
:
980 _collect( c
, transform
)
982 _collect
.a_models
= set()
983 _collect
.entities
= []
987 transform_main
= cxr_object_context( \
988 context
.scene
.cxr_data
.scale_factor
, 0.0 )
990 transform_sky
= cxr_object_context( \
991 context
.scene
.cxr_data
.skybox_scale_factor
, \
992 context
.scene
.cxr_data
.skybox_offset
)
994 if 'main' in bpy
.data
.collections
:
995 _collect( bpy
.data
.collections
['main'], transform_main
)
997 if 'skybox' in bpy
.data
.collections
:
998 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1000 def _buildsolid( obj
, ctx
):
1003 baked
= mesh_cxr_format( brush
[0] )
1004 world
= libcxr_decompose
.call( baked
, None )
1009 vmfinfo
.scale
= brush
[1].scale
1010 vmfinfo
.offset
[0] = 0.0
1011 vmfinfo
.offset
[1] = 0.0
1012 vmfinfo
.offset
[2] = brush
[1].offset_z
1014 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1015 libcxr_free_world
.call( world
)
1020 for brush
in _collect
.geo
:
1021 if not _buildsolid( brush
[0], brush
[1] ):
1022 libcxr_batch_debug_lines()
1024 return {'CANCELLED'}
1029 for entity
in _collect
.entities
:
1034 m
.kv( 'classname', cls
)
1036 kvs
= cxr_entity_keyvalues( obj
, ctx
, cls
)
1039 if isinstance(kv
[2], list):
1040 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1041 else: m
.kv( kv
[0], str(kv
[2]) )
1043 if not _buildsolid( obj
, ctx
):
1044 libcxr_batch_debug_lines()
1046 return {'CANCELLED'}
1050 print( "[CONVEXER] Compile materials / textures" )
1052 for mat
in material_info
.references
:
1053 compile_material(mat
)
1055 print( "[CONVEXER] Compiling models" )
1057 libcxr_batch_debug_lines()
1062 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1063 bl_idname
="convexer.dev_test"
1064 bl_label
="Export development data"
1066 def execute(_
,context
):
1069 # Prepare input data
1070 mesh_src
= mesh_cxr_format(context
.active_object
)
1072 libcxr_reset_debug_lines()
1073 libcxr_write_test_data
.call( pointer(mesh_src
) )
1074 libcxr_batch_debug_lines()
1079 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1080 bl_idname
="convexer.preview"
1081 bl_label
="Preview Brushes"
1086 def execute(_
,context
):
1089 def modal(_
,context
,event
):
1090 global debug_gpu_mesh
1091 static
= _
.__class
__
1093 if event
.type == 'ESC':
1094 libcxr_reset_debug_lines()
1095 libcxr_batch_debug_lines()
1096 debug_gpu_mesh
= None
1099 static
.RUNNING
= False
1102 return {'PASS_THROUGH'}
1104 def invoke(_
,context
,event
):
1105 global debug_gpu_shader
, debug_gpu_mesh
1106 static
= _
.__class
__
1107 static
.LASTERR
= None
1110 libcxr_reset_debug_lines()
1112 mesh_src
= mesh_cxr_format(context
.active_object
)
1115 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1118 debug_gpu_mesh
= None
1119 libcxr_batch_debug_lines()
1122 static
.LASTERR
= ["There is no error", \
1128 "Non-Convex Polygon"]\
1132 return {'CANCELLED'}
1134 context
.window_manager
.modal_handler_add(_
)
1135 return {'RUNNING_MODAL'}
1137 ptrpreview
= libcxr_world_preview
.call( world
)
1138 preview
= ptrpreview
[0]
1140 vertices
= preview
.vertices
[:preview
.vertex_count
]
1141 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1143 colours
= preview
.colours
[:preview
.vertex_count
]
1144 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1146 indices
= preview
.indices
[:preview
.indices_count
]
1147 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1148 for i
in range(int(preview
.indices_count
/3)) ]
1150 debug_gpu_mesh
= batch_for_shader(
1151 debug_gpu_shader
, 'TRIS',
1152 { "pos": vertices
, "color": colours
},
1156 libcxr_free_tri_mesh
.call( ptrpreview
)
1157 libcxr_free_world
.call( world
)
1158 libcxr_batch_debug_lines()
1162 return {'CANCELLED'}
1163 if not static
.RUNNING
:
1164 static
.RUNNING
= True
1165 context
.window_manager
.modal_handler_add(_
)
1166 return {'RUNNING_MODAL'}
1168 class CXR_VIEW3D( bpy
.types
.Panel
):
1169 bl_idname
= "VIEW3D_PT_convexer"
1170 bl_label
= "Convexer"
1171 bl_space_type
= 'VIEW_3D'
1172 bl_region_type
= 'UI'
1173 bl_category
= "Convexer"
1176 def poll(cls
, context
):
1177 return (context
.object is not None)
1179 def draw(_
, context
):
1183 row
.operator("convexer.preview")
1185 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
1187 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
1189 class CXR_INTERFACE(bpy
.types
.Panel
):
1191 bl_idname
="SCENE_PT_convexer"
1192 bl_space_type
='PROPERTIES'
1193 bl_region_type
='WINDOW'
1196 def draw(_
,context
):
1197 _
.layout
.operator("convexer.reload")
1198 _
.layout
.operator("convexer.dev_test")
1199 _
.layout
.operator("convexer.preview")
1200 _
.layout
.operator("convexer.write_vmf")
1202 settings
= context
.scene
.cxr_data
1204 _
.layout
.prop(settings
, "debug")
1205 _
.layout
.prop(settings
, "scale_factor")
1206 _
.layout
.prop(settings
, "lightmap_scale")
1207 _
.layout
.prop(settings
, "light_scale" )
1209 box
= _
.layout
.box()
1211 box
.prop(settings
, "project_name")
1212 box
.prop(settings
, "subdir")
1214 box
= _
.layout
.box()
1215 box
.operator("convexer.detect_compilers")
1216 box
.prop(settings
, "exe_studiomdl")
1217 box
.prop(settings
, "exe_vbsp")
1218 box
.prop(settings
, "exe_vvis")
1219 box
.prop(settings
, "exe_vrad")
1221 # COmpile image using NBVTF and hash it
1222 def compile_image(img
,flags
):
1226 name
= asset_name(img
)
1227 src_path
= bpy
.path
.abspath(img
.filepath
)
1229 dims
= img
.cxr_data
.export_res
1231 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888
,
1232 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1233 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1234 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1235 }[ img
.cxr_data
.fmt
]
1237 mipmap
= img
.cxr_data
.mipmap
1238 lod
= img
.cxr_data
.lod
1239 clamp
= img
.cxr_data
.clamp
1241 userflag_hash
= F
"{mipmap}.{lod}.{clamp}"
1242 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1243 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}"
1245 if img
.cxr_data
.last_hash
!= comphash
:
1246 print( F
"Texture update: {img.filepath}" )
1248 src
= src_path
.encode('utf-8')
1249 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1253 # texture setting flags
1254 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1256 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1257 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1259 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,0,flags_full
,dst
):
1260 img
.cxr_data
.last_hash
= comphash
1264 def compile_material(mat
):
1265 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1267 info
= material_info(mat
)
1268 properties
= mat
.cxr_data
1272 def _mlayer( layer
):
1273 nonlocal properties
, props
1276 if isinstance(layer
[decl
],dict): # $property definition
1278 ptype
= pdef
['type']
1284 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1287 # Group expansion (does it have subdefinitions?)
1289 if isinstance(pdef
[ch
],dict):
1298 if ptype
== 'intrinsic':
1302 prop
= getattr(properties
,decl
)
1303 default
= pdef
['default']
1305 if not isinstance(prop
,str) and \
1306 not isinstance(prop
,bpy
.types
.Image
) and \
1307 hasattr(prop
,'__getitem__'):
1308 prop
= tuple([p
for p
in prop
])
1312 props
+= [(decl
,pdef
,prop
)]
1317 if expandview
: _mlayer(pdef
)
1319 _mlayer( cxr_shader_params
)
1321 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1322 vmt
.node( properties
.shader
)
1323 vmt
.put( "// Convexer export\n" )
1333 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1336 if isinstance(prop
,bpy
.types
.Image
):
1339 flags
= pdef
['flags']
1340 vmt
.kv( decl
,compile_image(prop
,flags
))
1342 elif isinstance(prop
,bool):
1343 vmt
.kv( decl
, '1' if prop
else '0' )
1344 elif isinstance(prop
,str):
1345 vmt
.kv( decl
, prop
)
1346 elif isinstance(prop
,float) or isinstance(prop
,int):
1347 vmt
.kv( decl
, _numeric(prop
) )
1348 elif isinstance(prop
,tuple):
1349 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1351 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1355 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1356 bl_label
="VMT Properties"
1357 bl_idname
="SCENE_PT_convexer_vmt"
1358 bl_space_type
='PROPERTIES'
1359 bl_region_type
='WINDOW'
1360 bl_context
="material"
1362 def draw(_
,context
):
1363 active_object
= bpy
.context
.active_object
1364 if active_object
== None: return
1366 active_material
= active_object
.active_material
1367 if active_material
== None: return
1369 properties
= active_material
.cxr_data
1370 info
= material_info( active_material
)
1372 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1373 _
.layout
.prop( properties
, "shader" )
1376 _
.layout
.label(text
=F
"{xk}:={info[xk]}")
1378 def _mtex( name
, img
, uiParent
):
1381 box
= uiParent
.box()
1382 box
.label( text
=F
'{name} "{img.filepath}"' )
1384 if ((x
& (x
- 1)) == 0):
1387 closest_diff
= 10000000
1389 dist
= abs((1 << i
)-x
)
1390 if dist
< closest_diff
:
1395 return 1 << (closest
+1)
1397 return 1 << (closest
-1)
1402 row
.prop( img
.cxr_data
, "export_res" )
1403 row
.prop( img
.cxr_data
, "fmt" )
1406 row
.prop( img
.cxr_data
, "mipmap" )
1407 row
.prop( img
.cxr_data
, "lod" )
1408 row
.prop( img
.cxr_data
, "clamp" )
1410 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1411 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1413 def _mview( layer
, uiParent
):
1417 if isinstance(layer
[decl
],dict): # $property definition
1419 ptype
= pdef
['type']
1425 if ('shaders' in pdef
) and \
1426 (properties
.shader
not in pdef
['shaders']):
1429 if ptype
== 'intrinsic':
1430 if decl
not in info
:
1435 if isinstance(pdef
[ch
],dict):
1436 if ptype
== 'ui' or ptype
== 'intrinsic':
1438 elif getattr(properties
,decl
) == pdef
['default']:
1441 thisnode
= uiParent
.box()
1445 thisnode
.label( text
=decl
)
1446 elif ptype
== 'intrinsic':
1447 if isinstance(info
[decl
], bpy
.types
.Image
):
1448 _mtex( decl
, info
[decl
], thisnode
)
1450 # hidden intrinsic value.
1451 # Means its a float array or something not an image
1452 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
1454 thisnode
.prop(properties
,decl
)
1455 if expandview
: _mview(pdef
,thisnode
)
1457 _mview( cxr_shader_params
, _
.layout
)
1459 def cxr_entity_changeclass(_
,context
):
1460 active_object
= context
.active_object
1462 # Create ID properties
1464 classname
= active_object
.cxr_data
.classname
1466 if classname
in cxr_entities
:
1467 entdef
= cxr_entities
[classname
]
1469 kvs
= entdef
['keyvalues']
1470 if callable(kvs
): kvs
= kvs(active_object
)
1476 if callable(kv
) or not isinstance(kv
,dict): continue
1478 if key
not in active_object
:
1479 active_object
[key
] = kv
['default']
1480 id_prop
= active_object
.id_properties_ui(key
)
1481 id_prop
.update(default
=kv
['default'])
1483 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
1484 bl_label
="Entity Config"
1485 bl_idname
="SCENE_PT_convexer_entity"
1486 bl_space_type
='PROPERTIES'
1487 bl_region_type
='WINDOW'
1490 def draw(_
,context
):
1491 active_object
= bpy
.context
.active_object
1493 if active_object
== None: return
1495 default_context
= cxr_object_context( \
1496 bpy
.context
.scene
.cxr_data
.scale_factor
, 0.0 )
1498 ecn
= cxr_intrinsic_classname( active_object
)
1499 classname
= cxr_custom_class( active_object
)
1502 if active_object
.type == 'MESH':
1503 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
1504 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
1506 if classname
== 'NONE':
1509 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
1510 _
.layout
.enabled
=False
1513 kvs
= cxr_entity_keyvalues( active_object
, default_context
, classname
)
1517 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
1519 row
= _
.layout
.row()
1521 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
1523 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
1525 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
1526 bl_label
= "Source Settings"
1527 bl_idname
= "LIGHT_PT_cxr"
1528 bl_space_type
= 'PROPERTIES'
1529 bl_region_type
= 'WINDOW'
1532 def draw(self
, context
):
1533 layout
= self
.layout
1534 scene
= context
.scene
1536 active_object
= bpy
.context
.active_object
1537 if active_object
== None: return
1539 if active_object
.type == 'LIGHT' or \
1540 active_object
.type == 'LIGHT_PROBE':
1542 properties
= active_object
.data
.cxr_data
1544 if active_object
.type == 'LIGHT':
1545 layout
.prop( properties
, "realtime" )
1546 elif active_object
.type == 'LIGHT_PROBE':
1547 layout
.prop( properties
, "size" )
1549 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
1550 export_res
: bpy
.props
.IntVectorProperty(
1552 description
="Texture Export Resolution",
1558 fmt
: bpy
.props
.EnumProperty(
1561 ('DXT1', "DXT1", "BC1 compressed", '', 0),
1562 ('DXT5', "DXT5", "BC3 compressed", '', 1),
1563 ('RGB', "RGB", "Uncompressed", '', 2),
1564 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
1566 description
="Image format",
1569 last_hash
: bpy
.props
.StringProperty( name
="" )
1570 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1572 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
1573 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
1574 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
1576 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
1577 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
1579 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
1580 size
: bpy
.props
.EnumProperty(
1583 ('1',"1x1",'','',0),
1584 ('2',"2x2",'','',1),
1585 ('3',"4x4",'','',2),
1586 ('4',"8x8",'','',3),
1587 ('5',"16x16",'','',4),
1588 ('6',"32x32",'','',5),
1589 ('7',"64x64",'','',6),
1590 ('8',"128x128",'','',7),
1591 ('9',"256x256",'','',8)
1593 description
="Texture resolution",
1596 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
1597 entity
: bpy
.props
.BoolProperty(name
="")
1599 enum_pointents
= [('NONE',"None","")]
1600 enum_brushents
= [('NONE',"None","")]
1602 for classname
in cxr_entities
:
1603 entdef
= cxr_entities
[classname
]
1604 if 'allow' in entdef
:
1605 itm
= [(classname
, classname
, "")]
1606 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
1607 else: enum_brushents
+= itm
1609 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
1610 update
=cxr_entity_changeclass
, default
='NONE' )
1612 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
1613 update
=cxr_entity_changeclass
, default
='NONE' )
1615 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
1616 last_hash
: bpy
.props
.StringProperty( name
="" )
1617 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
1619 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
1620 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
1621 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
1623 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
1624 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
1625 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
1626 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
1627 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
1628 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
1629 opt_vrad
: bpy
.props
.StringProperty( name
="args" )
1631 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
1632 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
1633 default
=32.0,min=1.0)
1634 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
1635 default
=1.0,min=0.01)
1637 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
1638 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
1639 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
1641 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
1644 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1645 bl_idname
="convexer.detect_compilers"
1646 bl_label
="Find compilers"
1648 def execute(self
,context
):
1649 scene
= context
.scene
1650 settings
= scene
.cxr_data
1651 subdir
= settings
.subdir
1653 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1654 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1655 if os
.path
.exists(searchpath
):
1656 settings
[F
'exe_{exename}'] = searchpath
1660 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
1661 CXR_WRITE_VMF
, CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
1662 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
1663 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
1664 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
1668 global debug_draw_handler
, vmt_param_dynamic_class
1671 bpy
.utils
.register_class(c
)
1673 # Build dynamic VMT properties class defined by cxr_shader_params
1674 annotations_dict
= {}
1676 def _dvmt_propogate(layer
):
1677 nonlocal annotations_dict
1680 if isinstance(layer
[decl
],dict): # $property definition
1684 if pdef
['type'] == 'bool':
1685 prop
= bpy
.props
.BoolProperty(\
1686 name
= pdef
['name'],\
1687 default
= pdef
['default'])
1689 elif pdef
['type'] == 'float':
1690 prop
= bpy
.props
.FloatProperty(\
1691 name
= pdef
['name'],\
1692 default
= pdef
['default'])
1694 elif pdef
['type'] == 'vector':
1695 if 'subtype' in pdef
:
1696 prop
= bpy
.props
.FloatVectorProperty(\
1697 name
= pdef
['name'],\
1698 subtype
= pdef
['subtype'],\
1699 default
= pdef
['default'],\
1700 size
= len(pdef
['default']))
1702 prop
= bpy
.props
.FloatVectorProperty(\
1703 name
= pdef
['name'],\
1704 default
= pdef
['default'],\
1705 size
= len(pdef
['default']))
1707 elif pdef
['type'] == 'string':
1708 prop
= bpy
.props
.StringProperty(\
1709 name
= pdef
['name'],\
1710 default
= pdef
['default'])
1712 elif pdef
['type'] == 'enum':
1713 prop
= bpy
.props
.EnumProperty(\
1714 name
= pdef
['name'],\
1715 items
= pdef
['items'],\
1716 default
= pdef
['default'])
1719 annotations_dict
[decl
] = prop
1721 # Recurse into sub-definitions
1722 _dvmt_propogate(pdef
)
1724 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
1727 cxr_shaders
[_
]["name"],\
1728 '') for _
in cxr_shaders
],\
1729 default
= next(iter(cxr_shaders
)))
1731 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
1734 _dvmt_propogate( cxr_shader_params
)
1735 vmt_param_dynamic_class
= type(
1737 (bpy
.types
.PropertyGroup
,),{
1738 "__annotations__": annotations_dict
1742 bpy
.utils
.register_class( vmt_param_dynamic_class
)
1745 bpy
.types
.Material
.cxr_data
= \
1746 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
1747 bpy
.types
.Image
.cxr_data
= \
1748 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
1749 bpy
.types
.Object
.cxr_data
= \
1750 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
1751 bpy
.types
.Collection
.cxr_data
= \
1752 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
1753 bpy
.types
.Light
.cxr_data
= \
1754 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
1755 bpy
.types
.LightProbe
.cxr_data
= \
1756 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
1757 bpy
.types
.Scene
.cxr_data
= \
1758 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
1760 # CXR Scene settings
1763 debug_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
1764 cxr_draw
,(),'WINDOW','POST_VIEW')
1766 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
1767 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
1770 global debug_draw_handler
, vmt_param_dynamic_class
1772 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
1774 bpy
.utils
.unregister_class(c
)
1776 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
1777 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
1779 bpy
.types
.SpaceView3D
.draw_handler_remove(debug_draw_handler
,'WINDOW')