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 "light": { "keyvalues": ent_lights
},
640 "light_spot": { "keyvalues": ent_lights
},
642 "env_cubemap": { "keyvalues": ent_cubemap
},
650 "TeamNum": {"type": "int", "default": 0 }
655 def cxr_intrinsic_classname(obj
):
656 if obj
.type == 'LIGHT':
658 'SPOT': "light_spot",
660 'SUN': "light_directional" }[ obj
.data
.type ]
662 elif obj
.type == 'LIGHT_PROBE':
664 elif obj
.type == 'EMPTY':
670 def cxr_custom_class(obj
):
671 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
672 else: custom_class
= obj
.cxr_data
.classname
676 def cxr_classname(obj
):
677 intr
= cxr_intrinsic_classname(obj
)
678 if intr
!= None: return intr
680 custom_class
= cxr_custom_class(obj
)
681 if custom_class
!= 'NONE':
687 # intinsic: (k, False, value)
688 # property: (k, True, value or default)
692 def cxr_entity_keyvalues(obj
,context
,classname
):
693 if classname
not in cxr_entities
: return None
697 entdef
= cxr_entities
[classname
]
698 kvs
= entdef
['keyvalues']
700 if callable(kvs
): kvs
= kvs(obj
, context
)
707 if isinstance(kv
,dict):
709 value
= obj
[ F
"cxrkv_{k}" ]
712 value
= kv(obj
,context
)
714 if isinstance(value
,mathutils
.Vector
):
715 value
= [_
for _
in value
]
717 result
+= [(k
, isprop
, value
)]
721 def material_info(mat
):
723 info
['res'] = (512,512)
724 info
['name'] = 'tools/toolsnodraw'
726 if mat
== None or mat
.use_nodes
== False:
730 if mat
.cxr_data
.shader
== 'Builtin':
731 info
['name'] = mat
.name
734 if not hasattr(material_info
,'references'):
735 material_info
.references
= set()
738 material_info
.references
.add(mat
)
739 info
['name'] = asset_name(mat
)
741 # Using the cxr_graph_mapping as a reference, go through the shader
742 # graph and gather all $props from it.
744 def _graph_read( node_def
, node
=None, depth
=0 ):
748 def _variant_apply( val
):
751 if isinstance( val
, str ):
754 for shader_variant
in val
:
755 if shader_variant
[0] == mat
.cxr_data
.shader
:
756 return shader_variant
[1]
760 _graph_read
.extracted
= []
762 for node_idname
in node_def
:
763 for n
in mat
.node_tree
.nodes
:
764 if n
.bl_idname
== node_idname
:
765 node_def
= node_def
[node_idname
]
769 for link
in node_def
:
770 if isinstance( node_def
[link
], dict ):
771 inputt
= node
.inputs
[link
]
772 inputt_def
= node_def
[link
]
776 # look for definitions for the connected node type
777 con
= inputt
.links
[0].from_node
779 for node_idname
in inputt_def
:
780 if con
.bl_idname
== node_idname
:
781 con_def
= inputt_def
[ node_idname
]
782 _graph_read( con_def
, con
, depth
+1 )
784 # No definition found! :(
785 # TODO: Make a warning for this?
788 if "default" in inputt_def
:
789 prop
= _variant_apply( inputt_def
['default'] )
790 info
[prop
] = inputt
.default_value
792 prop
= _variant_apply( node_def
[link
] )
793 info
[prop
] = getattr(node
,link
)
795 _graph_read(cxr_graph_mapping
)
797 if "$basetexture" in info
:
798 export_res
= info
['$basetexture'].cxr_data
.export_res
799 info
['res'] = (export_res
[0], export_res
[1])
803 def mesh_cxr_format(obj
):
804 dgraph
= bpy
.context
.evaluated_depsgraph_get()
805 data
= obj
.evaluated_get(dgraph
).data
807 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
809 mesh
= cxr_input_mesh()
811 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
812 for i
, vert
in enumerate(data
.vertices
):
813 v
= obj
.matrix_world
@ vert
.co
814 vertex_data
[i
][0] = c_double(v
[0])
815 vertex_data
[i
][1] = c_double(v
[1])
816 vertex_data
[i
][2] = c_double(v
[2])
818 loop_data
= (cxr_input_loop
*len(data
.loops
))()
819 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
821 for i
, poly
in enumerate(data
.polygons
):
822 loop_start
= poly
.loop_start
823 loop_end
= poly
.loop_start
+ poly
.loop_total
824 for loop_index
in range(loop_start
, loop_end
):
825 loop
= data
.loops
[loop_index
]
826 loop_data
[loop_index
].index
= loop
.vertex_index
827 loop_data
[loop_index
].edge_index
= loop
.edge_index
830 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
831 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
832 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
834 loop_data
[loop_index
].uv
[0] = c_double(0.0)
835 loop_data
[loop_index
].uv
[1] = c_double(0.0)
836 center
= obj
.matrix_world
@ poly
.center
837 normal
= mtx_rot
@ poly
.normal
839 polygon_data
[i
].loop_start
= poly
.loop_start
840 polygon_data
[i
].loop_total
= poly
.loop_total
841 polygon_data
[i
].normal
[0] = normal
[0]
842 polygon_data
[i
].normal
[1] = normal
[1]
843 polygon_data
[i
].normal
[2] = normal
[2]
844 polygon_data
[i
].center
[0] = center
[0]
845 polygon_data
[i
].center
[1] = center
[1]
846 polygon_data
[i
].center
[2] = center
[2]
847 polygon_data
[i
].material_id
= poly
.material_index
849 edge_data
= (cxr_edge
*len(data
.edges
))()
851 for i
, edge
in enumerate(data
.edges
):
852 edge_data
[i
].i0
= edge
.vertices
[0]
853 edge_data
[i
].i1
= edge
.vertices
[1]
854 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
856 material_data
= (cxr_material
*len(obj
.material_slots
))()
858 for i
, ms
in enumerate(obj
.material_slots
):
859 inf
= material_info(ms
.material
)
860 material_data
[i
].res
[0] = inf
['res'][0]
861 material_data
[i
].res
[1] = inf
['res'][1]
862 material_data
[i
].vmt_path
= inf
['name'].encode('utf-8')
864 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
865 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
866 mesh
.loops
= cast(loop_data
,POINTER(cxr_input_loop
))
867 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
868 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
870 mesh
.poly_count
= len(data
.polygons
)
871 mesh
.vertex_count
= len(data
.vertices
)
872 mesh
.edge_count
= len(data
.edges
)
873 mesh
.loop_count
= len(data
.loops
)
874 mesh
.material_count
= len(obj
.material_slots
)
878 class CXR_WRITE_VMF(bpy
.types
.Operator
):
879 bl_idname
="convexer.write_vmf"
882 def execute(_
,context
):
884 libcxr_reset_debug_lines()
886 # Setup output and state
887 filepath
= bpy
.data
.filepath
888 directory
= os
.path
.dirname(filepath
)
889 settings
= context
.scene
.cxr_data
891 asset_dir
= F
"{directory}/bin"
892 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
893 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
895 os
.makedirs( asset_dir
, exist_ok
=True )
896 os
.makedirs( material_dir
, exist_ok
=True )
897 os
.makedirs( model_dir
, exist_ok
=True )
900 material_info
.references
= set()
901 libcxr_context_reset()
903 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
905 with
vdf_structure(output_vmf
) as m
:
906 print( F
"Write: {output_vmf}" )
908 m
.node('versioninfo')
909 m
.kv('editorversion','400')
910 m
.kv('editorbuild','8456')
911 m
.kv('mapversion','4')
912 m
.kv('formatversion','100')
919 m
.node('viewsettings')
920 m
.kv('bSnapToGrid','1')
921 m
.kv('bShowGrid','1')
922 m
.kv('bShowLogicalGrid','0')
923 m
.kv('nGridSpacing','64')
924 m
.kv('bShow3DGrid','0')
929 m
.kv('mapversion','1')
930 m
.kv('classname','worldspawn')
931 m
.kv('skyname','sky_csgo_night02b')
932 m
.kv('maxpropscreenwidth','-1')
933 m
.kv('detailvbsp','detail.vbsp')
934 m
.kv('detailmaterial','detail/detailsprites')
936 # Make sure all of our asset types have a unique ID
937 def _uid_prepare(objtype
):
943 if vs
.asset_id
in used_ids
:
946 id_max
= max(id_max
,vs
.asset_id
)
947 used_ids
+=[vs
.asset_id
]
948 for vs
in to_generate
:
952 _uid_prepare(bpy
.data
.materials
)
953 _uid_prepare(bpy
.data
.images
)
954 _uid_prepare(bpy
.data
.collections
)
956 # Export Brushes and displacement
957 def _collect(collection
,transform
):
958 if collection
.name
.startswith('.'):
961 if collection
.name
.startswith('mdl_'):
962 _collect
.heros
+= [(collection
,transform
)]
965 for obj
in collection
.objects
:
966 classname
= cxr_classname( obj
)
968 if classname
!= None:
969 _collect
.entities
+= [( obj
,transform
,classname
)]
970 elif obj
.type == 'MESH':
971 _collect
.geo
+= [(obj
,transform
)]
973 for c
in collection
.children
:
974 _collect( c
, transform
)
976 _collect
.a_models
= set()
977 _collect
.entities
= []
980 transform_main
= cxr_object_context( context
.scene
.cxr_data
.scale_factor
, 0.0 )
981 transform_sky
= cxr_object_context( context
.scene
.cxr_data
.skybox_scale_factor
, \
982 context
.scene
.cxr_data
.skybox_offset
)
984 if 'main' in bpy
.data
.collections
:
985 _collect( bpy
.data
.collections
['main'], transform_main
)
987 if 'skybox' in bpy
.data
.collections
:
988 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
991 for brush
in _collect
.geo
:
992 baked
= mesh_cxr_format( brush
[0] )
993 libcxr_set_scale_factor( brush
[1].scale
)
994 libcxr_set_offset( brush
[1].offset_z
)
995 libcxr_convert_mesh_to_vmf(baked
,m
.fp
)
1000 for entity
in _collect
.entities
:
1005 m
.kv( 'classname', cls
)
1007 kvs
= cxr_entity_keyvalues( obj
, ctx
, cls
)
1010 if isinstance(kv
[2], list):
1011 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1012 else: m
.kv( kv
[0], str(kv
[2]) )
1014 if obj
.type == 'MESH':
1015 baked
= mesh_cxr_format( obj
)
1016 libcxr_set_scale_factor( ctx
.scale
)
1017 libcxr_set_offset( ctx
.offset_z
)
1018 libcxr_convert_mesh_to_vmf(baked
,m
.fp
)
1022 print( "[CONVEXER] Compile materials / textures" )
1024 for mat
in material_info
.references
:
1025 compile_material(mat
)
1027 print( "[CONVEXER] Compiling models" )
1029 libcxr_batch_debug_lines()
1034 class CXR_DECOMPOSE_SOLID(bpy
.types
.Operator
):
1035 bl_idname
="convexer.decompose_solid"
1036 bl_label
="Decompose Solid"
1038 def execute(_
,context
):
1041 # Prepare input data
1042 mesh_src
= mesh_cxr_format(context
.active_object
)
1044 libcxr_reset_debug_lines()
1045 libcxr_decompose( pointer(mesh_src
) )
1046 libcxr_batch_debug_lines()
1051 class CXR_INTERFACE(bpy
.types
.Panel
):
1053 bl_idname
="SCENE_PT_convexer"
1054 bl_space_type
='PROPERTIES'
1055 bl_region_type
='WINDOW'
1058 def draw(_
,context
):
1059 _
.layout
.operator("convexer.reload")
1060 _
.layout
.operator("convexer.decompose_solid")
1061 _
.layout
.operator("convexer.write_vmf")
1063 settings
= context
.scene
.cxr_data
1065 _
.layout
.prop(settings
, "debug")
1066 _
.layout
.prop(settings
, "scale_factor")
1067 _
.layout
.prop(settings
, "lightmap_scale")
1068 _
.layout
.prop(settings
, "light_scale" )
1070 box
= _
.layout
.box()
1072 box
.prop(settings
, "project_name")
1073 box
.prop(settings
, "subdir")
1075 box
= _
.layout
.box()
1076 box
.operator("convexer.detect_compilers")
1077 box
.prop(settings
, "exe_studiomdl")
1078 box
.prop(settings
, "exe_vbsp")
1079 box
.prop(settings
, "exe_vvis")
1080 box
.prop(settings
, "exe_vrad")
1082 # COmpile image using NBVTF and hash it
1083 def compile_image(img
,flags
):
1087 name
= asset_name(img
)
1088 src_path
= bpy
.path
.abspath(img
.filepath
)
1090 dims
= img
.cxr_data
.export_res
1092 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888
,
1093 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1094 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1095 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1096 }[ img
.cxr_data
.fmt
]
1098 mipmap
= img
.cxr_data
.mipmap
1099 lod
= img
.cxr_data
.lod
1100 clamp
= img
.cxr_data
.clamp
1102 userflag_hash
= F
"{mipmap}.{lod}.{clamp}"
1103 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1104 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}"
1106 if img
.cxr_data
.last_hash
!= comphash
:
1107 print( F
"Texture update: {img.filepath}" )
1109 src
= src_path
.encode('utf-8')
1110 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1114 # texture setting flags
1115 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1117 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1118 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1120 if libnbvtf_convert(src
,dims
[0],dims
[1],mipmap
,fmt
,flags_full
,dst
):
1121 img
.cxr_data
.last_hash
= comphash
1125 def compile_material(mat
):
1126 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1128 info
= material_info(mat
)
1129 properties
= mat
.cxr_data
1133 def _mlayer( layer
):
1134 nonlocal properties
, props
1137 if isinstance(layer
[decl
],dict): # $property definition
1139 ptype
= pdef
['type']
1145 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1148 # Group expansion (does it have subdefinitions?)
1150 if isinstance(pdef
[ch
],dict):
1159 if ptype
== 'intrinsic':
1163 prop
= getattr(properties
,decl
)
1164 default
= pdef
['default']
1166 if not isinstance(prop
,str) and \
1167 not isinstance(prop
,bpy
.types
.Image
) and \
1168 hasattr(prop
,'__getitem__'):
1169 prop
= tuple([p
for p
in prop
])
1173 props
+= [(decl
,pdef
,prop
)]
1178 if expandview
: _mlayer(pdef
)
1180 _mlayer( cxr_shader_params
)
1182 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1183 vmt
.node( properties
.shader
)
1184 vmt
.put( "// Convexer export\n" )
1194 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1197 if isinstance(prop
,bpy
.types
.Image
):
1200 flags
= pdef
['flags']
1201 vmt
.kv( decl
,compile_image(prop
,flags
))
1203 elif isinstance(prop
,bool):
1204 vmt
.kv( decl
, '1' if prop
else '0' )
1205 elif isinstance(prop
,str):
1206 vmt
.kv( decl
, prop
)
1207 elif isinstance(prop
,float) or isinstance(prop
,int):
1208 vmt
.kv( decl
, _numeric(prop
) )
1209 elif isinstance(prop
,tuple):
1210 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1212 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1216 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1217 bl_label
="VMT Properties"
1218 bl_idname
="SCENE_PT_convexer_vmt"
1219 bl_space_type
='PROPERTIES'
1220 bl_region_type
='WINDOW'
1221 bl_context
="material"
1223 def draw(_
,context
):
1224 active_object
= bpy
.context
.active_object
1225 if active_object
== None: return
1227 active_material
= active_object
.active_material
1228 if active_material
== None: return
1230 properties
= active_material
.cxr_data
1231 info
= material_info( active_material
)
1233 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1234 _
.layout
.prop( properties
, "shader" )
1237 _
.layout
.label(text
=F
"{xk}:={info[xk]}")
1239 def _mtex( name
, img
, uiParent
):
1242 box
= uiParent
.box()
1243 box
.label( text
=F
'{name} "{img.filepath}"' )
1245 if ((x
& (x
- 1)) == 0):
1248 closest_diff
= 10000000
1250 dist
= abs((1 << i
)-x
)
1251 if dist
< closest_diff
:
1256 return 1 << (closest
+1)
1258 return 1 << (closest
-1)
1263 row
.prop( img
.cxr_data
, "export_res" )
1264 row
.prop( img
.cxr_data
, "fmt" )
1267 row
.prop( img
.cxr_data
, "mipmap" )
1268 row
.prop( img
.cxr_data
, "lod" )
1269 row
.prop( img
.cxr_data
, "clamp" )
1271 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1272 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1274 def _mview( layer
, uiParent
):
1278 if isinstance(layer
[decl
],dict): # $property definition
1280 ptype
= pdef
['type']
1286 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1289 if ptype
== 'intrinsic':
1290 if decl
not in info
:
1295 if isinstance(pdef
[ch
],dict):
1296 if ptype
== 'ui' or ptype
== 'intrinsic':
1298 elif getattr(properties
,decl
) == pdef
['default']:
1301 thisnode
= uiParent
.box()
1305 thisnode
.label( text
=decl
)
1306 elif ptype
== 'intrinsic':
1307 if isinstance(info
[decl
], bpy
.types
.Image
):
1308 _mtex( decl
, info
[decl
], thisnode
)
1310 # hidden intrinsic value.
1311 # Means its a float array or something not an image
1312 thisnode
.label( text
=F
"-- hidden intrinsic '{decl}' --" )
1314 thisnode
.prop(properties
,decl
)
1315 if expandview
: _mview(pdef
,thisnode
)
1317 _mview( cxr_shader_params
, _
.layout
)
1319 def cxr_entity_changeclass(_
,context
):
1320 active_object
= context
.active_object
1322 # Create ID properties
1324 classname
= active_object
.cxr_data
.classname
1326 if classname
in cxr_entities
:
1327 entdef
= cxr_entities
[classname
]
1329 kvs
= entdef
['keyvalues']
1330 if callable(kvs
): kvs
= kvs(active_object
)
1336 if callable(kv
) or not isinstance(kv
,dict): continue
1338 if key
not in active_object
:
1339 active_object
[key
] = kv
['default']
1340 id_prop
= active_object
.id_properties_ui(key
)
1341 id_prop
.update(default
=kv
['default'])
1343 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
1344 bl_label
="Entity Config"
1345 bl_idname
="SCENE_PT_convexer_entity"
1346 bl_space_type
='PROPERTIES'
1347 bl_region_type
='WINDOW'
1350 def draw(_
,context
):
1351 active_object
= bpy
.context
.active_object
1353 if active_object
== None: return
1355 default_context
= cxr_object_context( bpy
.context
.scene
.cxr_data
.scale_factor
, 0.0 )
1356 ecn
= cxr_intrinsic_classname( active_object
)
1357 classname
= cxr_custom_class( active_object
)
1360 if active_object
.type == 'MESH': _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
1361 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
1363 if classname
== 'NONE':
1366 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
1367 _
.layout
.enabled
=False
1370 kvs
= cxr_entity_keyvalues( active_object
, default_context
, classname
)
1374 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
1376 row
= _
.layout
.row()
1378 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
1380 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
1382 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
1383 bl_label
= "Source Settings"
1384 bl_idname
= "LIGHT_PT_cxr"
1385 bl_space_type
= 'PROPERTIES'
1386 bl_region_type
= 'WINDOW'
1389 def draw(self
, context
):
1390 layout
= self
.layout
1391 scene
= context
.scene
1393 active_object
= bpy
.context
.active_object
1394 if active_object
== None: return
1396 if active_object
.type == 'LIGHT' or \
1397 active_object
.type == 'LIGHT_PROBE':
1399 properties
= active_object
.data
.cxr_data
1401 if active_object
.type == 'LIGHT':
1402 layout
.prop( properties
, "realtime" )
1403 elif active_object
.type == 'LIGHT_PROBE':
1404 layout
.prop( properties
, "size" )
1406 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
1407 export_res
: bpy
.props
.IntVectorProperty(
1409 description
="Texture Export Resolution",
1415 fmt
: bpy
.props
.EnumProperty(
1418 ('DXT1', "DXT1", "BC1 compressed", '', 0),
1419 ('DXT5', "DXT5", "BC3 compressed", '', 1),
1420 ('RGB', "RGB", "Uncompressed", '', 2),
1421 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
1423 description
="Image format",
1426 last_hash
: bpy
.props
.StringProperty( name
="" )
1427 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1429 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
1430 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
1431 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
1433 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
1434 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
1436 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
1437 size
: bpy
.props
.EnumProperty(
1440 ('1',"1x1",'','',0),
1441 ('2',"2x2",'','',1),
1442 ('3',"4x4",'','',2),
1443 ('4',"8x8",'','',3),
1444 ('5',"16x16",'','',4),
1445 ('6',"32x32",'','',5),
1446 ('7',"64x64",'','',6),
1447 ('8',"128x128",'','',7),
1448 ('9',"256x256",'','',8)
1450 description
="Texture resolution",
1453 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
1454 entity
: bpy
.props
.BoolProperty(name
="")
1456 enum_pointents
= [('NONE',"None","")]
1457 enum_brushents
= [('NONE',"None","")]
1459 for classname
in cxr_entities
:
1460 entdef
= cxr_entities
[classname
]
1461 if 'allow' in entdef
:
1462 itm
= [(classname
, classname
, "")]
1463 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
1464 else: enum_brushents
+= itm
1466 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
1467 update
=cxr_entity_changeclass
, default
='NONE' )
1469 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
1470 update
=cxr_entity_changeclass
, default
='NONE' )
1472 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
1473 last_hash
: bpy
.props
.StringProperty( name
="" )
1474 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
1476 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
1477 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
1478 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
1480 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
1481 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
1482 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
1483 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
1484 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
1485 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
1486 opt_vrad
: bpy
.props
.StringProperty( name
="args" )
1488 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
1489 scale_factor
: bpy
.props
.FloatProperty(name
="VMF Scale factor",default
=32.0,min=1.0)
1490 skybox_scale_factor
: bpy
.props
.FloatProperty(name
="Sky Scale factor",default
=1.0,min=0.01)
1491 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
1492 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
1493 displacement_cardinal
: bpy
.props
.BoolProperty(name
="Cardinal displacements",default
=True)
1494 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",default
=True)
1495 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",default
=12)
1497 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1498 bl_idname
="convexer.detect_compilers"
1499 bl_label
="Find compilers"
1501 def execute(self
,context
):
1502 scene
= context
.scene
1503 settings
= scene
.cxr_data
1504 subdir
= settings
.subdir
1506 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1507 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1508 if os
.path
.exists(searchpath
):
1509 settings
[F
'exe_{exename}'] = searchpath
1513 classes
= [ CXR_RELOAD
, CXR_DECOMPOSE_SOLID
, CXR_INTERFACE
, \
1514 CXR_WRITE_VMF
, CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
1515 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
1516 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
1517 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
]
1520 global debug_draw_handler
, vmt_param_dynamic_class
1523 bpy
.utils
.register_class(c
)
1525 # Build dynamic VMT properties class defined by cxr_shader_params
1526 annotations_dict
= {}
1528 def _dvmt_propogate(layer
):
1529 nonlocal annotations_dict
1532 if isinstance(layer
[decl
],dict): # $property definition
1536 if pdef
['type'] == 'bool':
1537 prop
= bpy
.props
.BoolProperty(\
1538 name
= pdef
['name'],\
1539 default
= pdef
['default'])
1541 elif pdef
['type'] == 'float':
1542 prop
= bpy
.props
.FloatProperty(\
1543 name
= pdef
['name'],\
1544 default
= pdef
['default'])
1546 elif pdef
['type'] == 'vector':
1547 if 'subtype' in pdef
:
1548 prop
= bpy
.props
.FloatVectorProperty(\
1549 name
= pdef
['name'],\
1550 subtype
= pdef
['subtype'],\
1551 default
= pdef
['default'],\
1552 size
= len(pdef
['default']))
1554 prop
= bpy
.props
.FloatVectorProperty(\
1555 name
= pdef
['name'],\
1556 default
= pdef
['default'],\
1557 size
= len(pdef
['default']))
1559 elif pdef
['type'] == 'string':
1560 prop
= bpy
.props
.StringProperty(\
1561 name
= pdef
['name'],\
1562 default
= pdef
['default'])
1564 elif pdef
['type'] == 'enum':
1565 prop
= bpy
.props
.EnumProperty(\
1566 name
= pdef
['name'],\
1567 items
= pdef
['items'],\
1568 default
= pdef
['default'])
1571 annotations_dict
[decl
] = prop
1573 # Recurse into sub-definitions
1574 _dvmt_propogate(pdef
)
1576 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
1579 cxr_shaders
[_
]["name"],\
1580 '') for _
in cxr_shaders
],\
1581 default
= next(iter(cxr_shaders
)))
1583 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1585 _dvmt_propogate( cxr_shader_params
)
1586 vmt_param_dynamic_class
= type(
1588 (bpy
.types
.PropertyGroup
,),{
1589 "__annotations__": annotations_dict
1593 bpy
.utils
.register_class( vmt_param_dynamic_class
)
1596 bpy
.types
.Material
.cxr_data
= \
1597 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
1598 bpy
.types
.Image
.cxr_data
= \
1599 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
1600 bpy
.types
.Object
.cxr_data
= \
1601 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
1602 bpy
.types
.Collection
.cxr_data
= \
1603 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
1604 bpy
.types
.Light
.cxr_data
= \
1605 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
1606 bpy
.types
.LightProbe
.cxr_data
= \
1607 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
1608 bpy
.types
.Scene
.cxr_data
= \
1609 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
1611 # CXR Scene settings
1614 debug_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
1615 cxr_draw
,(),'WINDOW','POST_VIEW')
1617 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
1618 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
1621 global debug_draw_handler
, vmt_param_dynamic_class
1623 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
1625 bpy
.utils
.unregister_class(c
)
1627 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
1628 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
1630 bpy
.types
.SpaceView3D
.draw_handler_remove(debug_draw_handler
,'WINDOW')