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 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
31 libc_dlclose
.argtypes
= [c_void_p
]
33 c_libcxr_log_callback
= None
34 c_libcxr_line_callback
= None
36 libcxr_decompose
= None
37 libcxr_context_reset
= None
38 libcxr_set_offset
= None
39 libcxr_set_scale_factor
= None
41 # Vdf writing interface
42 libcxr_vdf_open
= None
43 libcxr_vdf_close
= None
45 libcxr_vdf_node
= None
46 libcxr_vdf_edon
= None
52 libnbvtf_convert
= None
57 NBVTF_IMAGE_FORMAT_RGBA8888
= 0
58 NBVTF_IMAGE_FORMAT_RGB888
= 2
59 NBVTF_IMAGE_FORMAT_DXT1
= 13
60 NBVTF_IMAGE_FORMAT_DXT5
= 15
61 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
62 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
63 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
64 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
65 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
67 # Wrapper for vdf functions to allow: with o = vdf_structure ...
68 class vdf_structure():
72 _
.fp
= libcxr_vdf_open( _
.path
.encode('utf-8') )
74 print( F
"Could not open file {_.path}" )
77 def __exit__(_
,type,value
,traceback
):
79 libcxr_vdf_close(_
.fp
)
82 libcxr_vdf_put(_
.fp
, s
.encode('utf-8') )
84 libcxr_vdf_node(_
.fp
, name
.encode('utf-8') )
88 libcxr_vdf_kv(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
90 class cxr_object_context():
91 def __init__(_
,scale
,offset_z
):
95 libcxr_convert_mesh_to_vmf
= None
97 debug_gpu_lines
= None
98 debug_gpu_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
99 debug_draw_handler
= None
101 class cxr_settings(Structure
):
102 _fields_
= [("debug",c_int32
),
103 ("lightmap_scale",c_int32
),
104 ("light_scale",c_double
)]
106 class cxr_input_loop(Structure
):
107 _fields_
= [("index",c_int32
),
108 ("edge_index",c_int32
),
111 class cxr_polygon(Structure
):
112 _fields_
= [("loop_start",c_int32
),
113 ("loop_total",c_int32
),
114 ("normal",c_double
* 3),
115 ("center",c_double
* 3),
116 ("material_id",c_int32
)]
118 class cxr_edge(Structure
):
119 _fields_
= [("i0",c_int32
),
121 ("freestyle",c_int32
)]
123 class cxr_material(Structure
):
124 _fields_
= [("res",c_int32
* 2),
125 ("vmt_path",c_char_p
)]
127 class cxr_input_mesh(Structure
):
128 _fields_
= [("vertices",POINTER(c_double
* 3)),
129 ("edges",POINTER(cxr_edge
)),
130 ("loops",POINTER(cxr_input_loop
)),
131 ("polys",POINTER(cxr_polygon
)),
132 ("materials",POINTER(cxr_material
)),
134 ("poly_count",c_int32
),
135 ("vertex_count",c_int32
),
136 ("edge_count",c_int32
),
137 ("loop_count",c_int32
),
138 ("material_count",c_int32
)]
140 class cxr_output_mesh(Structure
):
143 def libcxr_log_callback(logStr
):
144 print( F
"{logStr.decode('utf-8')}",end
='' )
146 debug_lines_positions
= None
147 debug_lines_colours
= None
149 def libcxr_reset_debug_lines():
150 global debug_lines_positions
151 global debug_lines_colours
153 debug_lines_positions
= []
154 debug_lines_colours
= []
156 def libcxr_batch_debug_lines():
157 global debug_lines_positions
158 global debug_lines_colours
159 global debug_gpu_lines
160 global debug_gpu_shader
162 debug_gpu_lines
= batch_for_shader(\
163 debug_gpu_shader
, 'LINES',\
164 { "pos": debug_lines_positions
, "color": debug_lines_colours
})
167 def cxr_on_load(dummy
):
168 libcxr_reset_debug_lines()
169 libcxr_batch_debug_lines()
172 def cxr_dgraph_update(scene
,dgraph
):
174 print( F
"Hallo {time.time()}" )
176 def libcxr_line_callback(p0
,p1
,colour
):
177 global debug_lines_positions
178 global debug_lines_colours
179 debug_lines_positions
+= [(p0
[0],p0
[1],p0
[2])]
180 debug_lines_positions
+= [(p1
[0],p1
[1],p1
[2])]
181 debug_lines_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
182 debug_lines_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
185 global debug_gpu_lines
186 global debug_gpu_shader
188 gpu
.state
.blend_set('ALPHA')
189 if debug_gpu_lines
!= None:
190 debug_gpu_lines
.draw(debug_gpu_shader
)
192 class CXR_RELOAD(bpy
.types
.Operator
):
193 bl_idname
="convexer.reload"
194 bl_label
="Reload convexer"
196 def execute(_
,context
):
197 global libcxr
, libnbvtf
, libnbvtf_convert
200 libnbvtf
= cdll
.LoadLibrary( os
.path
.dirname(__file__
)+'/libnbvtf.so')
201 libnbvtf_convert
= libnbvtf
.nbvtf_convert
202 libnbvtf_convert
.argtypes
= [\
210 libnbvtf_convert
.restype
= c_int32
213 _handle
= libcxr
._handle
215 # TODO: Find a propper way to do this
216 for i
in range(10): libc_dlclose( _handle
)
220 libcxr
= cdll
.LoadLibrary( os
.path
.dirname(__file__
)+'/libcxr.so')
222 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
223 print( F
"libcxr build time: {build_time.value}" )
226 global libcxr_decompose
227 global libcxr_convert_mesh_to_vmf
229 libcxr_decompose
= libcxr
.cxr_decompose
230 libcxr_decompose
.argtypes
= [\
231 POINTER(cxr_input_mesh
)
233 libcxr_decompose
.restype
= c_int32
235 libcxr_convert_mesh_to_vmf
= libcxr
.cxr_convert_mesh_to_vmf
236 libcxr_convert_mesh_to_vmf
.argtypes
= [\
237 POINTER(cxr_input_mesh
),\
240 libcxr_convert_mesh_to_vmf
.restype
= c_int32
242 global libcxr_context_reset
, libcxr_set_offset
,\
243 libcxr_set_scale_factor
244 libcxr_context_reset
= libcxr
.cxr_context_reset
246 libcxr_set_offset
= libcxr
.cxr_set_offset
247 libcxr_set_offset
.argtypes
= [ c_double
]
249 libcxr_set_scale_factor
= libcxr
.cxr_set_scale_factor
250 libcxr_set_scale_factor
.argtypes
= [ c_double
]
253 global libcxr_vdf_open
, \
260 libcxr_vdf_open
= libcxr
.cxr_vdf_open
261 libcxr_vdf_open
.argtypes
= [ c_char_p
]
262 libcxr_vdf_open
.restype
= c_void_p
264 libcxr_vdf_close
= libcxr
.cxr_vdf_close
265 libcxr_vdf_close
.argtypes
= [ c_void_p
]
267 libcxr_vdf_put
= libcxr
.cxr_vdf_put
268 libcxr_vdf_put
.argtypes
= [ c_void_p
, c_char_p
]
270 libcxr_vdf_node
= libcxr
.cxr_vdf_node
271 libcxr_vdf_node
.argtypes
= [ c_void_p
, c_char_p
]
273 libcxr_vdf_edon
= libcxr
.cxr_vdf_edon
274 libcxr_vdf_edon
.argtypes
= [ c_void_p
]
276 libcxr_vdf_kv
= libcxr
.cxr_vdf_kv
277 libcxr_vdf_kv
.argtypes
= [ c_void_p
, c_char_p
, c_char_p
]
280 global c_libcxr_log_callback
281 global c_libcxr_line_callback
283 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
284 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
285 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
287 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
288 POINTER(c_double
),POINTER(c_double
),POINTER(c_double
))
289 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
290 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
298 bpy
.ops
.convexer
.reload()
303 scene
= bpy
.context
.scene
306 settings
=cxr_settings()
307 settings
.debug
=_bool_int(cxr
.debug
)
309 settings
.lightmap_scale
=cxr
.lightmap_scale
310 settings
.light_scale
=cxr
.light_scale
312 libcxr
.cxr_settings_update(pointer(settings
))
320 dig
.append( int( v
% 5 ) )
323 ret
+= [ 'a','e','i','o','u' ][d
]
326 def asset_uid(asset
):
327 if isinstance(asset
,str):
329 name
= to_aeiou(asset
.cxr_data
.asset_id
)
330 if bpy
.context
.scene
.cxr_data
.include_names
:
331 name
+= asset
.name
.replace('.','_')
334 # -> <project_name>/<asset_name>
335 def asset_name(asset
):
336 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
338 # -> <subdir>/<project_name>/<asset_name>
339 def asset_path(subdir
, asset
):
340 return F
"{subdir}/{asset_name(asset_uid(asset))}"
342 # -> <csgo>/<subdir>/<project_name>/<asset_name>
343 def asset_full_path(sdir
,asset
):
344 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
345 F
"{asset_path(sdir,asset_uid(asset))}"
347 # view_layer.update() doesnt seem to work,
348 # tag_redraw() seems to have broken
349 # therefore, change a property
351 ob
= bpy
.context
.scene
.objects
[0]
352 ob
.hide_render
= ob
.hide_render
354 # the 'real' way to refresh the scene
355 #for area in bpy.context.window.screen.areas:
356 # if area.type == 'view_3d':
359 # The default shader is the first entry
362 "LightMappedGeneric":
364 "name": "Light Mapped",
369 "name": "Vertex Lit",
384 def material_tex_image(v
):
386 "ShaderNodeTexImage":
392 cxr_graph_mapping
= {
393 "ShaderNodeBsdfPrincipled":
399 "Color1": material_tex_image("basetexture"),
400 "Color2": material_tex_image("decaltexture")
402 "ShaderNodeTexImage":
404 "image":"$basetexture"
407 [("VertexLitGeneric","$color2"),\
408 ("UnlitGeneric","$color2"),\
409 ("LightMappedGeneric","$color")]
413 "ShaderNodeNormalMap":
415 "Color": material_tex_image("bumpmap")
421 cxr_shader_params
= {
425 "shaders": ("UnlitGeneric","VertexLitGeneric","LightMappedGeneric"),
429 "name": "Base Texture",
435 "name": "Decal Texture",
441 "name": "Blend Mode",
444 ('0',"AlphaOver","Default",'',0),
445 ('1',"Multiply","",'',1),
446 ('2',"Modulate","",'',2),
447 ('3',"Additive","",'',3)
455 "name": "Normal Map",
457 "flags": NBVTF_TEXTUREFLAGS_NORMAL
,
478 "shaders": ("VertexLitGeneric", "LightMappedGeneric"),
498 "$phongfresnelranges":
500 "name": "Fresnel Ranges",
502 "default":(1.0,1.0,1.0)
516 "default": (1.0,1.0,1.0)
520 "name": "Light Scale",
524 "$envmaplightscaleminmax":
535 "shaders": ("UnlitGeneric","VertexLitGeneric","LightMappedGeneric"),
539 "name": "Translucent",
545 "name": "Alpha Test",
549 "$alphatestreference":
565 def ent_get_origin(obj
,context
):
566 return obj
.location
* context
.scale
568 def ent_get_angles(obj
,context
):
569 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
576 def ent_baseclass(classes
, other
):
579 base
.update(x
.copy())
582 ent_origin
= { "origin": ent_get_origin
}
583 ent_angles
= { "angles": ent_get_angles
}
584 ent_transform
= ent_baseclass( [ent_origin
], ent_angles
)
586 def ent_lights(obj
,context
):
587 kvs
= ent_baseclass([ent_origin
],\
589 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
590 "_light": [int(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] + \
591 [int(obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
) ],
592 "_lightHDR": '-1 -1 -1 1',
596 if obj
.data
.type == 'SPOT':
597 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
598 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
600 # Blenders spotlights are -z forward
601 # Source is +x, however, it seems to use a completely different system.
602 # Since we dont care about roll for spotlights, we just take the
603 # pitch and yaw via trig
605 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
606 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
608 kvs
['pitch'] = math
.asin(fwd
[2]) * 57.295779513
609 kvs
['angles'] = [ 0.0, math
.atan2(fwd
[1],fwd
[0]) * 57.295779513, 0.0 ]
610 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look awful.
611 # Blender's default has a much more 'accurate' look
612 # They appear correct when using linear scale.
613 kvs
['_linear_attn'] = 1.0
615 elif obj
.data
.type == 'POINT':
616 kvs
['_quadratic_attn'] = 1.0
617 kvs
['_linear_attn'] = 0.0
619 elif obj
.data
.type == 'SUN':
624 def ent_cubemap(obj
,context
):
625 return ent_baseclass([ent_origin
],\
626 {"cubemapsize": obj
.data
.cxr_data
.size
})
629 "info_player_counterterrorist":
633 "keyvalues": ent_baseclass([ent_transform
],\
635 "priority": {"type": "int", "default": 0 },
636 "enabled": {"type": "int", "default": 1 },
639 "info_player_terrorist":
643 "keyvalues": ent_baseclass([ent_transform
],\
645 "priority": {"type": "int", "default": 0 },
646 "enabled": {"type": "int", "default": 1 },
649 "light": { "keyvalues": ent_lights
},
650 "light_spot": { "keyvalues": ent_lights
},
652 "env_cubemap": { "keyvalues": ent_cubemap
},
660 "TeamNum": {"type": "int", "default": 0 }
665 def cxr_intrinsic_classname(obj
):
666 if obj
.type == 'LIGHT':
668 'SPOT': "light_spot",
670 'SUN': "light_directional" }[ obj
.data
.type ]
672 elif obj
.type == 'LIGHT_PROBE':
674 elif obj
.type == 'EMPTY':
680 def cxr_custom_class(obj
):
681 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
682 else: custom_class
= obj
.cxr_data
.classname
686 def cxr_classname(obj
):
687 intr
= cxr_intrinsic_classname(obj
)
688 if intr
!= None: return intr
690 custom_class
= cxr_custom_class(obj
)
691 if custom_class
!= 'NONE':
697 # intinsic: (k, False, value)
698 # property: (k, True, value or default)
702 def cxr_entity_keyvalues(obj
,context
,classname
):
703 if classname
not in cxr_entities
: return None
707 entdef
= cxr_entities
[classname
]
708 kvs
= entdef
['keyvalues']
710 if callable(kvs
): kvs
= kvs(obj
, context
)
717 if isinstance(kv
,dict):
719 value
= obj
[ F
"cxrkv_{k}" ]
722 value
= kv(obj
,context
)
724 if isinstance(value
,mathutils
.Vector
):
725 value
= [_
for _
in value
]
727 result
+= [(k
, isprop
, value
)]
731 def material_info(mat
):
733 info
['res'] = (512,512)
734 info
['name'] = 'tools/toolsnodraw'
736 if mat
== None or mat
.use_nodes
== False:
740 if mat
.cxr_data
.shader
== 'Builtin':
741 info
['name'] = mat
.name
744 if not hasattr(material_info
,'references'):
745 material_info
.references
= set()
748 material_info
.references
.add(mat
)
749 info
['name'] = asset_name(mat
)
751 # Using the cxr_graph_mapping as a reference, go through the shader
752 # graph and gather all $props from it.
754 def _graph_read( node_def
, node
=None, depth
=0 ):
758 def _variant_apply( val
):
761 if isinstance( val
, str ):
764 for shader_variant
in val
:
765 if shader_variant
[0] == mat
.cxr_data
.shader
:
766 return shader_variant
[1]
770 _graph_read
.extracted
= []
772 for node_idname
in node_def
:
773 for n
in mat
.node_tree
.nodes
:
774 if n
.bl_idname
== node_idname
:
775 node_def
= node_def
[node_idname
]
779 for link
in node_def
:
780 if isinstance( node_def
[link
], dict ):
781 inputt
= node
.inputs
[link
]
782 inputt_def
= node_def
[link
]
786 # look for definitions for the connected node type
787 con
= inputt
.links
[0].from_node
789 for node_idname
in inputt_def
:
790 if con
.bl_idname
== node_idname
:
791 con_def
= inputt_def
[ node_idname
]
792 _graph_read( con_def
, con
, depth
+1 )
794 # No definition found! :(
795 # TODO: Make a warning for this?
798 if "default" in inputt_def
:
799 prop
= _variant_apply( inputt_def
['default'] )
800 info
[prop
] = inputt
.default_value
802 prop
= _variant_apply( node_def
[link
] )
803 info
[prop
] = getattr(node
,link
)
805 _graph_read(cxr_graph_mapping
)
807 if "$basetexture" in info
:
808 export_res
= info
['$basetexture'].cxr_data
.export_res
809 info
['res'] = (export_res
[0], export_res
[1])
813 def mesh_cxr_format(obj
):
814 dgraph
= bpy
.context
.evaluated_depsgraph_get()
815 data
= obj
.evaluated_get(dgraph
).data
817 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
819 mesh
= cxr_input_mesh()
821 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
822 for i
, vert
in enumerate(data
.vertices
):
823 v
= obj
.matrix_world
@ vert
.co
824 vertex_data
[i
][0] = c_double(v
[0])
825 vertex_data
[i
][1] = c_double(v
[1])
826 vertex_data
[i
][2] = c_double(v
[2])
828 loop_data
= (cxr_input_loop
*len(data
.loops
))()
829 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
831 for i
, poly
in enumerate(data
.polygons
):
832 loop_start
= poly
.loop_start
833 loop_end
= poly
.loop_start
+ poly
.loop_total
834 for loop_index
in range(loop_start
, loop_end
):
835 loop
= data
.loops
[loop_index
]
836 loop_data
[loop_index
].index
= loop
.vertex_index
837 loop_data
[loop_index
].edge_index
= loop
.edge_index
840 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
841 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
842 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
844 loop_data
[loop_index
].uv
[0] = c_double(0.0)
845 loop_data
[loop_index
].uv
[1] = c_double(0.0)
846 center
= obj
.matrix_world
@ poly
.center
847 normal
= mtx_rot
@ poly
.normal
849 polygon_data
[i
].loop_start
= poly
.loop_start
850 polygon_data
[i
].loop_total
= poly
.loop_total
851 polygon_data
[i
].normal
[0] = normal
[0]
852 polygon_data
[i
].normal
[1] = normal
[1]
853 polygon_data
[i
].normal
[2] = normal
[2]
854 polygon_data
[i
].center
[0] = center
[0]
855 polygon_data
[i
].center
[1] = center
[1]
856 polygon_data
[i
].center
[2] = center
[2]
857 polygon_data
[i
].material_id
= poly
.material_index
859 edge_data
= (cxr_edge
*len(data
.edges
))()
861 for i
, edge
in enumerate(data
.edges
):
862 edge_data
[i
].i0
= edge
.vertices
[0]
863 edge_data
[i
].i1
= edge
.vertices
[1]
864 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
866 material_data
= (cxr_material
*len(obj
.material_slots
))()
868 for i
, ms
in enumerate(obj
.material_slots
):
869 inf
= material_info(ms
.material
)
870 material_data
[i
].res
[0] = inf
['res'][0]
871 material_data
[i
].res
[1] = inf
['res'][1]
872 material_data
[i
].vmt_path
= inf
['name'].encode('utf-8')
874 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
875 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
876 mesh
.loops
= cast(loop_data
,POINTER(cxr_input_loop
))
877 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
878 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
880 mesh
.poly_count
= len(data
.polygons
)
881 mesh
.vertex_count
= len(data
.vertices
)
882 mesh
.edge_count
= len(data
.edges
)
883 mesh
.loop_count
= len(data
.loops
)
884 mesh
.material_count
= len(obj
.material_slots
)
888 class CXR_WRITE_VMF(bpy
.types
.Operator
):
889 bl_idname
="convexer.write_vmf"
892 def execute(_
,context
):
894 libcxr_reset_debug_lines()
896 # Setup output and state
897 filepath
= bpy
.data
.filepath
898 directory
= os
.path
.dirname(filepath
)
899 settings
= context
.scene
.cxr_data
901 asset_dir
= F
"{directory}/bin"
902 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
903 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
905 os
.makedirs( asset_dir
, exist_ok
=True )
906 os
.makedirs( material_dir
, exist_ok
=True )
907 os
.makedirs( model_dir
, exist_ok
=True )
910 material_info
.references
= set()
911 libcxr_context_reset()
913 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
915 with
vdf_structure(output_vmf
) as m
:
916 print( F
"Write: {output_vmf}" )
918 m
.node('versioninfo')
919 m
.kv('editorversion','400')
920 m
.kv('editorbuild','8456')
921 m
.kv('mapversion','4')
922 m
.kv('formatversion','100')
929 m
.node('viewsettings')
930 m
.kv('bSnapToGrid','1')
931 m
.kv('bShowGrid','1')
932 m
.kv('bShowLogicalGrid','0')
933 m
.kv('nGridSpacing','64')
934 m
.kv('bShow3DGrid','0')
939 m
.kv('mapversion','1')
940 m
.kv('classname','worldspawn')
941 m
.kv('skyname','sky_csgo_night02b')
942 m
.kv('maxpropscreenwidth','-1')
943 m
.kv('detailvbsp','detail.vbsp')
944 m
.kv('detailmaterial','detail/detailsprites')
946 # Make sure all of our asset types have a unique ID
947 def _uid_prepare(objtype
):
953 if vs
.asset_id
in used_ids
:
956 id_max
= max(id_max
,vs
.asset_id
)
957 used_ids
+=[vs
.asset_id
]
958 for vs
in to_generate
:
962 _uid_prepare(bpy
.data
.materials
)
963 _uid_prepare(bpy
.data
.images
)
964 _uid_prepare(bpy
.data
.collections
)
966 # Export Brushes and displacement
967 def _collect(collection
,transform
):
968 if collection
.name
.startswith('.'):
971 if collection
.hide_render
:
974 if collection
.name
.startswith('mdl_'):
975 _collect
.heros
+= [(collection
,transform
)]
978 for obj
in collection
.objects
:
979 if obj
.hide_get(): continue
981 classname
= cxr_classname( obj
)
983 if classname
!= None:
984 _collect
.entities
+= [( obj
,transform
,classname
)]
985 elif obj
.type == 'MESH':
986 _collect
.geo
+= [(obj
,transform
)]
988 for c
in collection
.children
:
989 _collect( c
, transform
)
991 _collect
.a_models
= set()
992 _collect
.entities
= []
996 transform_main
= cxr_object_context( context
.scene
.cxr_data
.scale_factor
, 0.0 )
997 transform_sky
= cxr_object_context( context
.scene
.cxr_data
.skybox_scale_factor
, \
998 context
.scene
.cxr_data
.skybox_offset
)
1000 if 'main' in bpy
.data
.collections
:
1001 _collect( bpy
.data
.collections
['main'], transform_main
)
1003 if 'skybox' in bpy
.data
.collections
:
1004 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1007 for brush
in _collect
.geo
:
1008 baked
= mesh_cxr_format( brush
[0] )
1009 libcxr_set_scale_factor( brush
[1].scale
)
1010 libcxr_set_offset( brush
[1].offset_z
)
1011 libcxr_convert_mesh_to_vmf(baked
,m
.fp
)
1016 for entity
in _collect
.entities
:
1021 m
.kv( 'classname', cls
)
1023 kvs
= cxr_entity_keyvalues( obj
, ctx
, cls
)
1026 if isinstance(kv
[2], list):
1027 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1028 else: m
.kv( kv
[0], str(kv
[2]) )
1030 if obj
.type == 'MESH':
1031 baked
= mesh_cxr_format( obj
)
1032 libcxr_set_scale_factor( ctx
.scale
)
1033 libcxr_set_offset( ctx
.offset_z
)
1034 libcxr_convert_mesh_to_vmf(baked
,m
.fp
)
1038 print( "[CONVEXER] Compile materials / textures" )
1040 for mat
in material_info
.references
:
1041 compile_material(mat
)
1043 print( "[CONVEXER] Compiling models" )
1045 libcxr_batch_debug_lines()
1050 class CXR_DECOMPOSE_SOLID(bpy
.types
.Operator
):
1051 bl_idname
="convexer.decompose_solid"
1052 bl_label
="Decompose Solid"
1054 def execute(_
,context
):
1057 # Prepare input data
1058 mesh_src
= mesh_cxr_format(context
.active_object
)
1060 libcxr_reset_debug_lines()
1061 libcxr_decompose( pointer(mesh_src
) )
1062 libcxr_batch_debug_lines()
1067 class CXR_INTERFACE(bpy
.types
.Panel
):
1069 bl_idname
="SCENE_PT_convexer"
1070 bl_space_type
='PROPERTIES'
1071 bl_region_type
='WINDOW'
1074 def draw(_
,context
):
1075 _
.layout
.operator("convexer.reload")
1076 _
.layout
.operator("convexer.decompose_solid")
1077 _
.layout
.operator("convexer.write_vmf")
1079 settings
= context
.scene
.cxr_data
1081 _
.layout
.prop(settings
, "debug")
1082 _
.layout
.prop(settings
, "scale_factor")
1083 _
.layout
.prop(settings
, "lightmap_scale")
1084 _
.layout
.prop(settings
, "light_scale" )
1086 box
= _
.layout
.box()
1088 box
.prop(settings
, "project_name")
1089 box
.prop(settings
, "subdir")
1091 box
= _
.layout
.box()
1092 box
.operator("convexer.detect_compilers")
1093 box
.prop(settings
, "exe_studiomdl")
1094 box
.prop(settings
, "exe_vbsp")
1095 box
.prop(settings
, "exe_vvis")
1096 box
.prop(settings
, "exe_vrad")
1098 # COmpile image using NBVTF and hash it
1099 def compile_image(img
,flags
):
1103 name
= asset_name(img
)
1104 src_path
= bpy
.path
.abspath(img
.filepath
)
1106 dims
= img
.cxr_data
.export_res
1108 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888
,
1109 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1110 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1111 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1112 }[ img
.cxr_data
.fmt
]
1114 mipmap
= img
.cxr_data
.mipmap
1115 lod
= img
.cxr_data
.lod
1116 clamp
= img
.cxr_data
.clamp
1118 userflag_hash
= F
"{mipmap}.{lod}.{clamp}"
1119 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1120 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}"
1122 if img
.cxr_data
.last_hash
!= comphash
:
1123 print( F
"Texture update: {img.filepath}" )
1125 src
= src_path
.encode('utf-8')
1126 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1130 # texture setting flags
1131 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1133 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1134 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1136 if libnbvtf_convert(src
,dims
[0],dims
[1],mipmap
,fmt
,flags_full
,dst
):
1137 img
.cxr_data
.last_hash
= comphash
1141 def compile_material(mat
):
1142 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1144 info
= material_info(mat
)
1145 properties
= mat
.cxr_data
1149 def _mlayer( layer
):
1150 nonlocal properties
, props
1153 if isinstance(layer
[decl
],dict): # $property definition
1155 ptype
= pdef
['type']
1161 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1164 # Group expansion (does it have subdefinitions?)
1166 if isinstance(pdef
[ch
],dict):
1175 if ptype
== 'intrinsic':
1179 prop
= getattr(properties
,decl
)
1180 default
= pdef
['default']
1182 if not isinstance(prop
,str) and \
1183 not isinstance(prop
,bpy
.types
.Image
) and \
1184 hasattr(prop
,'__getitem__'):
1185 prop
= tuple([p
for p
in prop
])
1189 props
+= [(decl
,pdef
,prop
)]
1194 if expandview
: _mlayer(pdef
)
1196 _mlayer( cxr_shader_params
)
1198 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1199 vmt
.node( properties
.shader
)
1200 vmt
.put( "// Convexer export\n" )
1210 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1213 if isinstance(prop
,bpy
.types
.Image
):
1216 flags
= pdef
['flags']
1217 vmt
.kv( decl
,compile_image(prop
,flags
))
1219 elif isinstance(prop
,bool):
1220 vmt
.kv( decl
, '1' if prop
else '0' )
1221 elif isinstance(prop
,str):
1222 vmt
.kv( decl
, prop
)
1223 elif isinstance(prop
,float) or isinstance(prop
,int):
1224 vmt
.kv( decl
, _numeric(prop
) )
1225 elif isinstance(prop
,tuple):
1226 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1228 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1232 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1233 bl_label
="VMT Properties"
1234 bl_idname
="SCENE_PT_convexer_vmt"
1235 bl_space_type
='PROPERTIES'
1236 bl_region_type
='WINDOW'
1237 bl_context
="material"
1239 def draw(_
,context
):
1240 active_object
= bpy
.context
.active_object
1241 if active_object
== None: return
1243 active_material
= active_object
.active_material
1244 if active_material
== None: return
1246 properties
= active_material
.cxr_data
1247 info
= material_info( active_material
)
1249 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1250 _
.layout
.prop( properties
, "shader" )
1253 _
.layout
.label(text
=F
"{xk}:={info[xk]}")
1255 def _mtex( name
, img
, uiParent
):
1258 box
= uiParent
.box()
1259 box
.label( text
=F
'{name} "{img.filepath}"' )
1261 if ((x
& (x
- 1)) == 0):
1264 closest_diff
= 10000000
1266 dist
= abs((1 << i
)-x
)
1267 if dist
< closest_diff
:
1272 return 1 << (closest
+1)
1274 return 1 << (closest
-1)
1279 row
.prop( img
.cxr_data
, "export_res" )
1280 row
.prop( img
.cxr_data
, "fmt" )
1283 row
.prop( img
.cxr_data
, "mipmap" )
1284 row
.prop( img
.cxr_data
, "lod" )
1285 row
.prop( img
.cxr_data
, "clamp" )
1287 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1288 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1290 def _mview( layer
, uiParent
):
1294 if isinstance(layer
[decl
],dict): # $property definition
1296 ptype
= pdef
['type']
1302 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1305 if ptype
== 'intrinsic':
1306 if decl
not in info
:
1311 if isinstance(pdef
[ch
],dict):
1312 if ptype
== 'ui' or ptype
== 'intrinsic':
1314 elif getattr(properties
,decl
) == pdef
['default']:
1317 thisnode
= uiParent
.box()
1321 thisnode
.label( text
=decl
)
1322 elif ptype
== 'intrinsic':
1323 if isinstance(info
[decl
], bpy
.types
.Image
):
1324 _mtex( decl
, info
[decl
], thisnode
)
1326 # hidden intrinsic value.
1327 # Means its a float array or something not an image
1328 thisnode
.label( text
=F
"-- hidden intrinsic '{decl}' --" )
1330 thisnode
.prop(properties
,decl
)
1331 if expandview
: _mview(pdef
,thisnode
)
1333 _mview( cxr_shader_params
, _
.layout
)
1335 def cxr_entity_changeclass(_
,context
):
1336 active_object
= context
.active_object
1338 # Create ID properties
1340 classname
= active_object
.cxr_data
.classname
1342 if classname
in cxr_entities
:
1343 entdef
= cxr_entities
[classname
]
1345 kvs
= entdef
['keyvalues']
1346 if callable(kvs
): kvs
= kvs(active_object
)
1352 if callable(kv
) or not isinstance(kv
,dict): continue
1354 if key
not in active_object
:
1355 active_object
[key
] = kv
['default']
1356 id_prop
= active_object
.id_properties_ui(key
)
1357 id_prop
.update(default
=kv
['default'])
1359 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
1360 bl_label
="Entity Config"
1361 bl_idname
="SCENE_PT_convexer_entity"
1362 bl_space_type
='PROPERTIES'
1363 bl_region_type
='WINDOW'
1366 def draw(_
,context
):
1367 active_object
= bpy
.context
.active_object
1369 if active_object
== None: return
1371 default_context
= cxr_object_context( bpy
.context
.scene
.cxr_data
.scale_factor
, 0.0 )
1372 ecn
= cxr_intrinsic_classname( active_object
)
1373 classname
= cxr_custom_class( active_object
)
1376 if active_object
.type == 'MESH': _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
1377 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
1379 if classname
== 'NONE':
1382 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
1383 _
.layout
.enabled
=False
1386 kvs
= cxr_entity_keyvalues( active_object
, default_context
, classname
)
1390 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
1392 row
= _
.layout
.row()
1394 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
1396 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
1398 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
1399 bl_label
= "Source Settings"
1400 bl_idname
= "LIGHT_PT_cxr"
1401 bl_space_type
= 'PROPERTIES'
1402 bl_region_type
= 'WINDOW'
1405 def draw(self
, context
):
1406 layout
= self
.layout
1407 scene
= context
.scene
1409 active_object
= bpy
.context
.active_object
1410 if active_object
== None: return
1412 if active_object
.type == 'LIGHT' or \
1413 active_object
.type == 'LIGHT_PROBE':
1415 properties
= active_object
.data
.cxr_data
1417 if active_object
.type == 'LIGHT':
1418 layout
.prop( properties
, "realtime" )
1419 elif active_object
.type == 'LIGHT_PROBE':
1420 layout
.prop( properties
, "size" )
1422 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
1423 export_res
: bpy
.props
.IntVectorProperty(
1425 description
="Texture Export Resolution",
1431 fmt
: bpy
.props
.EnumProperty(
1434 ('DXT1', "DXT1", "BC1 compressed", '', 0),
1435 ('DXT5', "DXT5", "BC3 compressed", '', 1),
1436 ('RGB', "RGB", "Uncompressed", '', 2),
1437 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
1439 description
="Image format",
1442 last_hash
: bpy
.props
.StringProperty( name
="" )
1443 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1445 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
1446 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
1447 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
1449 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
1450 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
1452 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
1453 size
: bpy
.props
.EnumProperty(
1456 ('1',"1x1",'','',0),
1457 ('2',"2x2",'','',1),
1458 ('3',"4x4",'','',2),
1459 ('4',"8x8",'','',3),
1460 ('5',"16x16",'','',4),
1461 ('6',"32x32",'','',5),
1462 ('7',"64x64",'','',6),
1463 ('8',"128x128",'','',7),
1464 ('9',"256x256",'','',8)
1466 description
="Texture resolution",
1469 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
1470 entity
: bpy
.props
.BoolProperty(name
="")
1472 enum_pointents
= [('NONE',"None","")]
1473 enum_brushents
= [('NONE',"None","")]
1475 for classname
in cxr_entities
:
1476 entdef
= cxr_entities
[classname
]
1477 if 'allow' in entdef
:
1478 itm
= [(classname
, classname
, "")]
1479 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
1480 else: enum_brushents
+= itm
1482 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
1483 update
=cxr_entity_changeclass
, default
='NONE' )
1485 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
1486 update
=cxr_entity_changeclass
, default
='NONE' )
1488 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
1489 last_hash
: bpy
.props
.StringProperty( name
="" )
1490 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
1492 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
1493 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
1494 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
1496 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
1497 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
1498 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
1499 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
1500 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
1501 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
1502 opt_vrad
: bpy
.props
.StringProperty( name
="args" )
1504 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
1505 scale_factor
: bpy
.props
.FloatProperty(name
="VMF Scale factor",default
=32.0,min=1.0)
1506 skybox_scale_factor
: bpy
.props
.FloatProperty(name
="Sky Scale factor",default
=1.0,min=0.01)
1507 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
1508 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
1509 displacement_cardinal
: bpy
.props
.BoolProperty(name
="Cardinal displacements",default
=True)
1510 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",default
=True)
1511 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",default
=12)
1513 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1514 bl_idname
="convexer.detect_compilers"
1515 bl_label
="Find compilers"
1517 def execute(self
,context
):
1518 scene
= context
.scene
1519 settings
= scene
.cxr_data
1520 subdir
= settings
.subdir
1522 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1523 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1524 if os
.path
.exists(searchpath
):
1525 settings
[F
'exe_{exename}'] = searchpath
1529 classes
= [ CXR_RELOAD
, CXR_DECOMPOSE_SOLID
, CXR_INTERFACE
, \
1530 CXR_WRITE_VMF
, CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
1531 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
1532 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
1533 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
]
1536 global debug_draw_handler
, vmt_param_dynamic_class
1539 bpy
.utils
.register_class(c
)
1541 # Build dynamic VMT properties class defined by cxr_shader_params
1542 annotations_dict
= {}
1544 def _dvmt_propogate(layer
):
1545 nonlocal annotations_dict
1548 if isinstance(layer
[decl
],dict): # $property definition
1552 if pdef
['type'] == 'bool':
1553 prop
= bpy
.props
.BoolProperty(\
1554 name
= pdef
['name'],\
1555 default
= pdef
['default'])
1557 elif pdef
['type'] == 'float':
1558 prop
= bpy
.props
.FloatProperty(\
1559 name
= pdef
['name'],\
1560 default
= pdef
['default'])
1562 elif pdef
['type'] == 'vector':
1563 if 'subtype' in pdef
:
1564 prop
= bpy
.props
.FloatVectorProperty(\
1565 name
= pdef
['name'],\
1566 subtype
= pdef
['subtype'],\
1567 default
= pdef
['default'],\
1568 size
= len(pdef
['default']))
1570 prop
= bpy
.props
.FloatVectorProperty(\
1571 name
= pdef
['name'],\
1572 default
= pdef
['default'],\
1573 size
= len(pdef
['default']))
1575 elif pdef
['type'] == 'string':
1576 prop
= bpy
.props
.StringProperty(\
1577 name
= pdef
['name'],\
1578 default
= pdef
['default'])
1580 elif pdef
['type'] == 'enum':
1581 prop
= bpy
.props
.EnumProperty(\
1582 name
= pdef
['name'],\
1583 items
= pdef
['items'],\
1584 default
= pdef
['default'])
1587 annotations_dict
[decl
] = prop
1589 # Recurse into sub-definitions
1590 _dvmt_propogate(pdef
)
1592 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
1595 cxr_shaders
[_
]["name"],\
1596 '') for _
in cxr_shaders
],\
1597 default
= next(iter(cxr_shaders
)))
1599 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1601 _dvmt_propogate( cxr_shader_params
)
1602 vmt_param_dynamic_class
= type(
1604 (bpy
.types
.PropertyGroup
,),{
1605 "__annotations__": annotations_dict
1609 bpy
.utils
.register_class( vmt_param_dynamic_class
)
1612 bpy
.types
.Material
.cxr_data
= \
1613 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
1614 bpy
.types
.Image
.cxr_data
= \
1615 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
1616 bpy
.types
.Object
.cxr_data
= \
1617 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
1618 bpy
.types
.Collection
.cxr_data
= \
1619 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
1620 bpy
.types
.Light
.cxr_data
= \
1621 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
1622 bpy
.types
.LightProbe
.cxr_data
= \
1623 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
1624 bpy
.types
.Scene
.cxr_data
= \
1625 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
1627 # CXR Scene settings
1630 debug_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
1631 cxr_draw
,(),'WINDOW','POST_VIEW')
1633 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
1634 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
1637 global debug_draw_handler
, vmt_param_dynamic_class
1639 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
1641 bpy
.utils
.unregister_class(c
)
1643 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
1644 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
1646 bpy
.types
.SpaceView3D
.draw_handler_remove(debug_draw_handler
,'WINDOW')