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
.hide_render
:
964 if collection
.name
.startswith('mdl_'):
965 _collect
.heros
+= [(collection
,transform
)]
968 for obj
in collection
.objects
:
969 if obj
.hide_get(): continue
971 classname
= cxr_classname( obj
)
973 if classname
!= None:
974 _collect
.entities
+= [( obj
,transform
,classname
)]
975 elif obj
.type == 'MESH':
976 _collect
.geo
+= [(obj
,transform
)]
978 for c
in collection
.children
:
979 _collect( c
, transform
)
981 _collect
.a_models
= set()
982 _collect
.entities
= []
986 transform_main
= cxr_object_context( context
.scene
.cxr_data
.scale_factor
, 0.0 )
987 transform_sky
= cxr_object_context( context
.scene
.cxr_data
.skybox_scale_factor
, \
988 context
.scene
.cxr_data
.skybox_offset
)
990 if 'main' in bpy
.data
.collections
:
991 _collect( bpy
.data
.collections
['main'], transform_main
)
993 if 'skybox' in bpy
.data
.collections
:
994 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
997 for brush
in _collect
.geo
:
998 baked
= mesh_cxr_format( brush
[0] )
999 libcxr_set_scale_factor( brush
[1].scale
)
1000 libcxr_set_offset( brush
[1].offset_z
)
1001 libcxr_convert_mesh_to_vmf(baked
,m
.fp
)
1006 for entity
in _collect
.entities
:
1011 m
.kv( 'classname', cls
)
1013 kvs
= cxr_entity_keyvalues( obj
, ctx
, cls
)
1016 if isinstance(kv
[2], list):
1017 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1018 else: m
.kv( kv
[0], str(kv
[2]) )
1020 if obj
.type == 'MESH':
1021 baked
= mesh_cxr_format( obj
)
1022 libcxr_set_scale_factor( ctx
.scale
)
1023 libcxr_set_offset( ctx
.offset_z
)
1024 libcxr_convert_mesh_to_vmf(baked
,m
.fp
)
1028 print( "[CONVEXER] Compile materials / textures" )
1030 for mat
in material_info
.references
:
1031 compile_material(mat
)
1033 print( "[CONVEXER] Compiling models" )
1035 libcxr_batch_debug_lines()
1040 class CXR_DECOMPOSE_SOLID(bpy
.types
.Operator
):
1041 bl_idname
="convexer.decompose_solid"
1042 bl_label
="Decompose Solid"
1044 def execute(_
,context
):
1047 # Prepare input data
1048 mesh_src
= mesh_cxr_format(context
.active_object
)
1050 libcxr_reset_debug_lines()
1051 libcxr_decompose( pointer(mesh_src
) )
1052 libcxr_batch_debug_lines()
1057 class CXR_INTERFACE(bpy
.types
.Panel
):
1059 bl_idname
="SCENE_PT_convexer"
1060 bl_space_type
='PROPERTIES'
1061 bl_region_type
='WINDOW'
1064 def draw(_
,context
):
1065 _
.layout
.operator("convexer.reload")
1066 _
.layout
.operator("convexer.decompose_solid")
1067 _
.layout
.operator("convexer.write_vmf")
1069 settings
= context
.scene
.cxr_data
1071 _
.layout
.prop(settings
, "debug")
1072 _
.layout
.prop(settings
, "scale_factor")
1073 _
.layout
.prop(settings
, "lightmap_scale")
1074 _
.layout
.prop(settings
, "light_scale" )
1076 box
= _
.layout
.box()
1078 box
.prop(settings
, "project_name")
1079 box
.prop(settings
, "subdir")
1081 box
= _
.layout
.box()
1082 box
.operator("convexer.detect_compilers")
1083 box
.prop(settings
, "exe_studiomdl")
1084 box
.prop(settings
, "exe_vbsp")
1085 box
.prop(settings
, "exe_vvis")
1086 box
.prop(settings
, "exe_vrad")
1088 # COmpile image using NBVTF and hash it
1089 def compile_image(img
,flags
):
1093 name
= asset_name(img
)
1094 src_path
= bpy
.path
.abspath(img
.filepath
)
1096 dims
= img
.cxr_data
.export_res
1098 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888
,
1099 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1100 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1101 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1102 }[ img
.cxr_data
.fmt
]
1104 mipmap
= img
.cxr_data
.mipmap
1105 lod
= img
.cxr_data
.lod
1106 clamp
= img
.cxr_data
.clamp
1108 userflag_hash
= F
"{mipmap}.{lod}.{clamp}"
1109 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1110 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}"
1112 if img
.cxr_data
.last_hash
!= comphash
:
1113 print( F
"Texture update: {img.filepath}" )
1115 src
= src_path
.encode('utf-8')
1116 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1120 # texture setting flags
1121 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1123 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1124 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1126 if libnbvtf_convert(src
,dims
[0],dims
[1],mipmap
,fmt
,flags_full
,dst
):
1127 img
.cxr_data
.last_hash
= comphash
1131 def compile_material(mat
):
1132 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1134 info
= material_info(mat
)
1135 properties
= mat
.cxr_data
1139 def _mlayer( layer
):
1140 nonlocal properties
, props
1143 if isinstance(layer
[decl
],dict): # $property definition
1145 ptype
= pdef
['type']
1151 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1154 # Group expansion (does it have subdefinitions?)
1156 if isinstance(pdef
[ch
],dict):
1165 if ptype
== 'intrinsic':
1169 prop
= getattr(properties
,decl
)
1170 default
= pdef
['default']
1172 if not isinstance(prop
,str) and \
1173 not isinstance(prop
,bpy
.types
.Image
) and \
1174 hasattr(prop
,'__getitem__'):
1175 prop
= tuple([p
for p
in prop
])
1179 props
+= [(decl
,pdef
,prop
)]
1184 if expandview
: _mlayer(pdef
)
1186 _mlayer( cxr_shader_params
)
1188 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1189 vmt
.node( properties
.shader
)
1190 vmt
.put( "// Convexer export\n" )
1200 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1203 if isinstance(prop
,bpy
.types
.Image
):
1206 flags
= pdef
['flags']
1207 vmt
.kv( decl
,compile_image(prop
,flags
))
1209 elif isinstance(prop
,bool):
1210 vmt
.kv( decl
, '1' if prop
else '0' )
1211 elif isinstance(prop
,str):
1212 vmt
.kv( decl
, prop
)
1213 elif isinstance(prop
,float) or isinstance(prop
,int):
1214 vmt
.kv( decl
, _numeric(prop
) )
1215 elif isinstance(prop
,tuple):
1216 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1218 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1222 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1223 bl_label
="VMT Properties"
1224 bl_idname
="SCENE_PT_convexer_vmt"
1225 bl_space_type
='PROPERTIES'
1226 bl_region_type
='WINDOW'
1227 bl_context
="material"
1229 def draw(_
,context
):
1230 active_object
= bpy
.context
.active_object
1231 if active_object
== None: return
1233 active_material
= active_object
.active_material
1234 if active_material
== None: return
1236 properties
= active_material
.cxr_data
1237 info
= material_info( active_material
)
1239 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1240 _
.layout
.prop( properties
, "shader" )
1243 _
.layout
.label(text
=F
"{xk}:={info[xk]}")
1245 def _mtex( name
, img
, uiParent
):
1248 box
= uiParent
.box()
1249 box
.label( text
=F
'{name} "{img.filepath}"' )
1251 if ((x
& (x
- 1)) == 0):
1254 closest_diff
= 10000000
1256 dist
= abs((1 << i
)-x
)
1257 if dist
< closest_diff
:
1262 return 1 << (closest
+1)
1264 return 1 << (closest
-1)
1269 row
.prop( img
.cxr_data
, "export_res" )
1270 row
.prop( img
.cxr_data
, "fmt" )
1273 row
.prop( img
.cxr_data
, "mipmap" )
1274 row
.prop( img
.cxr_data
, "lod" )
1275 row
.prop( img
.cxr_data
, "clamp" )
1277 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1278 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1280 def _mview( layer
, uiParent
):
1284 if isinstance(layer
[decl
],dict): # $property definition
1286 ptype
= pdef
['type']
1292 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1295 if ptype
== 'intrinsic':
1296 if decl
not in info
:
1301 if isinstance(pdef
[ch
],dict):
1302 if ptype
== 'ui' or ptype
== 'intrinsic':
1304 elif getattr(properties
,decl
) == pdef
['default']:
1307 thisnode
= uiParent
.box()
1311 thisnode
.label( text
=decl
)
1312 elif ptype
== 'intrinsic':
1313 if isinstance(info
[decl
], bpy
.types
.Image
):
1314 _mtex( decl
, info
[decl
], thisnode
)
1316 # hidden intrinsic value.
1317 # Means its a float array or something not an image
1318 thisnode
.label( text
=F
"-- hidden intrinsic '{decl}' --" )
1320 thisnode
.prop(properties
,decl
)
1321 if expandview
: _mview(pdef
,thisnode
)
1323 _mview( cxr_shader_params
, _
.layout
)
1325 def cxr_entity_changeclass(_
,context
):
1326 active_object
= context
.active_object
1328 # Create ID properties
1330 classname
= active_object
.cxr_data
.classname
1332 if classname
in cxr_entities
:
1333 entdef
= cxr_entities
[classname
]
1335 kvs
= entdef
['keyvalues']
1336 if callable(kvs
): kvs
= kvs(active_object
)
1342 if callable(kv
) or not isinstance(kv
,dict): continue
1344 if key
not in active_object
:
1345 active_object
[key
] = kv
['default']
1346 id_prop
= active_object
.id_properties_ui(key
)
1347 id_prop
.update(default
=kv
['default'])
1349 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
1350 bl_label
="Entity Config"
1351 bl_idname
="SCENE_PT_convexer_entity"
1352 bl_space_type
='PROPERTIES'
1353 bl_region_type
='WINDOW'
1356 def draw(_
,context
):
1357 active_object
= bpy
.context
.active_object
1359 if active_object
== None: return
1361 default_context
= cxr_object_context( bpy
.context
.scene
.cxr_data
.scale_factor
, 0.0 )
1362 ecn
= cxr_intrinsic_classname( active_object
)
1363 classname
= cxr_custom_class( active_object
)
1366 if active_object
.type == 'MESH': _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
1367 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
1369 if classname
== 'NONE':
1372 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
1373 _
.layout
.enabled
=False
1376 kvs
= cxr_entity_keyvalues( active_object
, default_context
, classname
)
1380 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
1382 row
= _
.layout
.row()
1384 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
1386 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
1388 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
1389 bl_label
= "Source Settings"
1390 bl_idname
= "LIGHT_PT_cxr"
1391 bl_space_type
= 'PROPERTIES'
1392 bl_region_type
= 'WINDOW'
1395 def draw(self
, context
):
1396 layout
= self
.layout
1397 scene
= context
.scene
1399 active_object
= bpy
.context
.active_object
1400 if active_object
== None: return
1402 if active_object
.type == 'LIGHT' or \
1403 active_object
.type == 'LIGHT_PROBE':
1405 properties
= active_object
.data
.cxr_data
1407 if active_object
.type == 'LIGHT':
1408 layout
.prop( properties
, "realtime" )
1409 elif active_object
.type == 'LIGHT_PROBE':
1410 layout
.prop( properties
, "size" )
1412 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
1413 export_res
: bpy
.props
.IntVectorProperty(
1415 description
="Texture Export Resolution",
1421 fmt
: bpy
.props
.EnumProperty(
1424 ('DXT1', "DXT1", "BC1 compressed", '', 0),
1425 ('DXT5', "DXT5", "BC3 compressed", '', 1),
1426 ('RGB', "RGB", "Uncompressed", '', 2),
1427 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
1429 description
="Image format",
1432 last_hash
: bpy
.props
.StringProperty( name
="" )
1433 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1435 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
1436 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
1437 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
1439 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
1440 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
1442 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
1443 size
: bpy
.props
.EnumProperty(
1446 ('1',"1x1",'','',0),
1447 ('2',"2x2",'','',1),
1448 ('3',"4x4",'','',2),
1449 ('4',"8x8",'','',3),
1450 ('5',"16x16",'','',4),
1451 ('6',"32x32",'','',5),
1452 ('7',"64x64",'','',6),
1453 ('8',"128x128",'','',7),
1454 ('9',"256x256",'','',8)
1456 description
="Texture resolution",
1459 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
1460 entity
: bpy
.props
.BoolProperty(name
="")
1462 enum_pointents
= [('NONE',"None","")]
1463 enum_brushents
= [('NONE',"None","")]
1465 for classname
in cxr_entities
:
1466 entdef
= cxr_entities
[classname
]
1467 if 'allow' in entdef
:
1468 itm
= [(classname
, classname
, "")]
1469 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
1470 else: enum_brushents
+= itm
1472 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
1473 update
=cxr_entity_changeclass
, default
='NONE' )
1475 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
1476 update
=cxr_entity_changeclass
, default
='NONE' )
1478 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
1479 last_hash
: bpy
.props
.StringProperty( name
="" )
1480 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
1482 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
1483 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
1484 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
1486 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
1487 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
1488 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
1489 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
1490 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
1491 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
1492 opt_vrad
: bpy
.props
.StringProperty( name
="args" )
1494 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
1495 scale_factor
: bpy
.props
.FloatProperty(name
="VMF Scale factor",default
=32.0,min=1.0)
1496 skybox_scale_factor
: bpy
.props
.FloatProperty(name
="Sky Scale factor",default
=1.0,min=0.01)
1497 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
1498 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
1499 displacement_cardinal
: bpy
.props
.BoolProperty(name
="Cardinal displacements",default
=True)
1500 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",default
=True)
1501 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",default
=12)
1503 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1504 bl_idname
="convexer.detect_compilers"
1505 bl_label
="Find compilers"
1507 def execute(self
,context
):
1508 scene
= context
.scene
1509 settings
= scene
.cxr_data
1510 subdir
= settings
.subdir
1512 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1513 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1514 if os
.path
.exists(searchpath
):
1515 settings
[F
'exe_{exename}'] = searchpath
1519 classes
= [ CXR_RELOAD
, CXR_DECOMPOSE_SOLID
, CXR_INTERFACE
, \
1520 CXR_WRITE_VMF
, CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
1521 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
1522 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
1523 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
]
1526 global debug_draw_handler
, vmt_param_dynamic_class
1529 bpy
.utils
.register_class(c
)
1531 # Build dynamic VMT properties class defined by cxr_shader_params
1532 annotations_dict
= {}
1534 def _dvmt_propogate(layer
):
1535 nonlocal annotations_dict
1538 if isinstance(layer
[decl
],dict): # $property definition
1542 if pdef
['type'] == 'bool':
1543 prop
= bpy
.props
.BoolProperty(\
1544 name
= pdef
['name'],\
1545 default
= pdef
['default'])
1547 elif pdef
['type'] == 'float':
1548 prop
= bpy
.props
.FloatProperty(\
1549 name
= pdef
['name'],\
1550 default
= pdef
['default'])
1552 elif pdef
['type'] == 'vector':
1553 if 'subtype' in pdef
:
1554 prop
= bpy
.props
.FloatVectorProperty(\
1555 name
= pdef
['name'],\
1556 subtype
= pdef
['subtype'],\
1557 default
= pdef
['default'],\
1558 size
= len(pdef
['default']))
1560 prop
= bpy
.props
.FloatVectorProperty(\
1561 name
= pdef
['name'],\
1562 default
= pdef
['default'],\
1563 size
= len(pdef
['default']))
1565 elif pdef
['type'] == 'string':
1566 prop
= bpy
.props
.StringProperty(\
1567 name
= pdef
['name'],\
1568 default
= pdef
['default'])
1570 elif pdef
['type'] == 'enum':
1571 prop
= bpy
.props
.EnumProperty(\
1572 name
= pdef
['name'],\
1573 items
= pdef
['items'],\
1574 default
= pdef
['default'])
1577 annotations_dict
[decl
] = prop
1579 # Recurse into sub-definitions
1580 _dvmt_propogate(pdef
)
1582 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
1585 cxr_shaders
[_
]["name"],\
1586 '') for _
in cxr_shaders
],\
1587 default
= next(iter(cxr_shaders
)))
1589 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1591 _dvmt_propogate( cxr_shader_params
)
1592 vmt_param_dynamic_class
= type(
1594 (bpy
.types
.PropertyGroup
,),{
1595 "__annotations__": annotations_dict
1599 bpy
.utils
.register_class( vmt_param_dynamic_class
)
1602 bpy
.types
.Material
.cxr_data
= \
1603 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
1604 bpy
.types
.Image
.cxr_data
= \
1605 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
1606 bpy
.types
.Object
.cxr_data
= \
1607 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
1608 bpy
.types
.Collection
.cxr_data
= \
1609 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
1610 bpy
.types
.Light
.cxr_data
= \
1611 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
1612 bpy
.types
.LightProbe
.cxr_data
= \
1613 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
1614 bpy
.types
.Scene
.cxr_data
= \
1615 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
1617 # CXR Scene settings
1620 debug_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
1621 cxr_draw
,(),'WINDOW','POST_VIEW')
1623 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
1624 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
1627 global debug_draw_handler
, vmt_param_dynamic_class
1629 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
1631 bpy
.utils
.unregister_class(c
)
1633 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
1634 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
1636 bpy
.types
.SpaceView3D
.draw_handler_remove(debug_draw_handler
,'WINDOW')