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_write_test_data
= 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_write_test_data
, libcxr_convert_mesh_to_vmf
228 libcxr_write_test_data
= libcxr
.cxr_write_test_data
229 libcxr_write_test_data
.argtypes
= [\
230 POINTER(cxr_input_mesh
)
232 libcxr_write_test_data
.restype
= c_int32
234 libcxr_convert_mesh_to_vmf
= libcxr
.cxr_convert_mesh_to_vmf
235 libcxr_convert_mesh_to_vmf
.argtypes
= [\
236 POINTER(cxr_input_mesh
),\
239 libcxr_convert_mesh_to_vmf
.restype
= c_int32
241 global libcxr_context_reset
, libcxr_set_offset
,\
242 libcxr_set_scale_factor
243 libcxr_context_reset
= libcxr
.cxr_context_reset
245 libcxr_set_offset
= libcxr
.cxr_set_offset
246 libcxr_set_offset
.argtypes
= [ c_double
]
248 libcxr_set_scale_factor
= libcxr
.cxr_set_scale_factor
249 libcxr_set_scale_factor
.argtypes
= [ c_double
]
252 global libcxr_vdf_open
, \
259 libcxr_vdf_open
= libcxr
.cxr_vdf_open
260 libcxr_vdf_open
.argtypes
= [ c_char_p
]
261 libcxr_vdf_open
.restype
= c_void_p
263 libcxr_vdf_close
= libcxr
.cxr_vdf_close
264 libcxr_vdf_close
.argtypes
= [ c_void_p
]
266 libcxr_vdf_put
= libcxr
.cxr_vdf_put
267 libcxr_vdf_put
.argtypes
= [ c_void_p
, c_char_p
]
269 libcxr_vdf_node
= libcxr
.cxr_vdf_node
270 libcxr_vdf_node
.argtypes
= [ c_void_p
, c_char_p
]
272 libcxr_vdf_edon
= libcxr
.cxr_vdf_edon
273 libcxr_vdf_edon
.argtypes
= [ c_void_p
]
275 libcxr_vdf_kv
= libcxr
.cxr_vdf_kv
276 libcxr_vdf_kv
.argtypes
= [ c_void_p
, c_char_p
, c_char_p
]
279 global c_libcxr_log_callback
280 global c_libcxr_line_callback
282 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
283 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
284 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
286 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
287 POINTER(c_double
),POINTER(c_double
),POINTER(c_double
))
288 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
289 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
297 bpy
.ops
.convexer
.reload()
302 scene
= bpy
.context
.scene
305 settings
=cxr_settings()
306 settings
.debug
=_bool_int(cxr
.debug
)
308 settings
.lightmap_scale
=cxr
.lightmap_scale
309 settings
.light_scale
=cxr
.light_scale
311 libcxr
.cxr_settings_update(pointer(settings
))
319 dig
.append( int( v
% 5 ) )
322 ret
+= [ 'a','e','i','o','u' ][d
]
325 def asset_uid(asset
):
326 if isinstance(asset
,str):
328 name
= to_aeiou(asset
.cxr_data
.asset_id
)
329 if bpy
.context
.scene
.cxr_data
.include_names
:
330 name
+= asset
.name
.replace('.','_')
333 # -> <project_name>/<asset_name>
334 def asset_name(asset
):
335 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
337 # -> <subdir>/<project_name>/<asset_name>
338 def asset_path(subdir
, asset
):
339 return F
"{subdir}/{asset_name(asset_uid(asset))}"
341 # -> <csgo>/<subdir>/<project_name>/<asset_name>
342 def asset_full_path(sdir
,asset
):
343 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
344 F
"{asset_path(sdir,asset_uid(asset))}"
346 # view_layer.update() doesnt seem to work,
347 # tag_redraw() seems to have broken
348 # therefore, change a property
350 ob
= bpy
.context
.scene
.objects
[0]
351 ob
.hide_render
= ob
.hide_render
353 # the 'real' way to refresh the scene
354 #for area in bpy.context.window.screen.areas:
355 # if area.type == 'view_3d':
358 # The default shader is the first entry
361 "LightMappedGeneric":
363 "name": "Light Mapped",
368 "name": "Vertex Lit",
383 def material_tex_image(v
):
385 "ShaderNodeTexImage":
391 cxr_graph_mapping
= {
392 "ShaderNodeBsdfPrincipled":
398 "Color1": material_tex_image("basetexture"),
399 "Color2": material_tex_image("decaltexture")
401 "ShaderNodeTexImage":
403 "image":"$basetexture"
406 [("VertexLitGeneric","$color2"),\
407 ("UnlitGeneric","$color2"),\
408 ("LightMappedGeneric","$color")]
412 "ShaderNodeNormalMap":
414 "Color": material_tex_image("bumpmap")
420 cxr_shader_params
= {
424 "shaders": ("UnlitGeneric","VertexLitGeneric","LightMappedGeneric"),
428 "name": "Base Texture",
434 "name": "Decal Texture",
440 "name": "Blend Mode",
443 ('0',"AlphaOver","Default",'',0),
444 ('1',"Multiply","",'',1),
445 ('2',"Modulate","",'',2),
446 ('3',"Additive","",'',3)
454 "name": "Normal Map",
456 "flags": NBVTF_TEXTUREFLAGS_NORMAL
,
477 "shaders": ("VertexLitGeneric", "LightMappedGeneric"),
497 "$phongfresnelranges":
499 "name": "Fresnel Ranges",
501 "default":(1.0,1.0,1.0)
515 "default": (1.0,1.0,1.0)
519 "name": "Light Scale",
523 "$envmaplightscaleminmax":
534 "shaders": ("UnlitGeneric","VertexLitGeneric","LightMappedGeneric"),
538 "name": "Translucent",
544 "name": "Alpha Test",
548 "$alphatestreference":
564 def ent_get_origin(obj
,context
):
565 return obj
.location
* context
.scale
567 def ent_get_angles(obj
,context
):
568 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
575 def ent_baseclass(classes
, other
):
578 base
.update(x
.copy())
581 ent_origin
= { "origin": ent_get_origin
}
582 ent_angles
= { "angles": ent_get_angles
}
583 ent_transform
= ent_baseclass( [ent_origin
], ent_angles
)
585 def ent_lights(obj
,context
):
586 kvs
= ent_baseclass([ent_origin
],\
588 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
589 "_light": [int(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
590 [int(obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
)],
591 "_lightHDR": '-1 -1 -1 1',
595 if obj
.data
.type == 'SPOT':
596 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
597 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
599 # Blenders spotlights are -z forward
600 # Source is +x, however, it seems to use a completely different system.
601 # Since we dont care about roll for spotlights, we just take the
602 # pitch and yaw via trig
604 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
605 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
607 kvs
['pitch'] = math
.asin(fwd
[2]) * 57.295779513
608 kvs
['angles'] = [ 0.0, math
.atan2(fwd
[1],fwd
[0]) * 57.295779513, 0.0 ]
609 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
612 # Blender's default has a much more 'nice'
614 kvs
['_linear_attn'] = 1.0
616 elif obj
.data
.type == 'POINT':
617 kvs
['_quadratic_attn'] = 1.0
618 kvs
['_linear_attn'] = 0.0
620 elif obj
.data
.type == 'SUN':
625 def ent_cubemap(obj
,context
):
626 return ent_baseclass([ent_origin
],\
627 {"cubemapsize": obj
.data
.cxr_data
.size
})
630 "info_player_counterterrorist":
634 "keyvalues": ent_baseclass([ent_transform
],\
636 "priority": {"type": "int", "default": 0 },
637 "enabled": {"type": "int", "default": 1 },
640 "info_player_terrorist":
644 "keyvalues": ent_baseclass([ent_transform
],\
646 "priority": {"type": "int", "default": 0 },
647 "enabled": {"type": "int", "default": 1 },
650 "light": { "keyvalues": ent_lights
},
651 "light_spot": { "keyvalues": ent_lights
},
653 "env_cubemap": { "keyvalues": ent_cubemap
},
661 "TeamNum": {"type": "int", "default": 0 }
666 def cxr_intrinsic_classname(obj
):
667 if obj
.type == 'LIGHT':
669 'SPOT': "light_spot",
671 'SUN': "light_directional" }[ obj
.data
.type ]
673 elif obj
.type == 'LIGHT_PROBE':
675 elif obj
.type == 'EMPTY':
681 def cxr_custom_class(obj
):
682 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
683 else: custom_class
= obj
.cxr_data
.classname
687 def cxr_classname(obj
):
688 intr
= cxr_intrinsic_classname(obj
)
689 if intr
!= None: return intr
691 custom_class
= cxr_custom_class(obj
)
692 if custom_class
!= 'NONE':
698 # intinsic: (k, False, value)
699 # property: (k, True, value or default)
703 def cxr_entity_keyvalues(obj
,context
,classname
):
704 if classname
not in cxr_entities
: return None
708 entdef
= cxr_entities
[classname
]
709 kvs
= entdef
['keyvalues']
711 if callable(kvs
): kvs
= kvs(obj
, context
)
718 if isinstance(kv
,dict):
720 value
= obj
[ F
"cxrkv_{k}" ]
723 value
= kv(obj
,context
)
725 if isinstance(value
,mathutils
.Vector
):
726 value
= [_
for _
in value
]
728 result
+= [(k
, isprop
, value
)]
732 def material_info(mat
):
734 info
['res'] = (512,512)
735 info
['name'] = 'tools/toolsnodraw'
737 if mat
== None or mat
.use_nodes
== False:
741 if mat
.cxr_data
.shader
== 'Builtin':
742 info
['name'] = mat
.name
745 if not hasattr(material_info
,'references'):
746 material_info
.references
= set()
749 material_info
.references
.add(mat
)
750 info
['name'] = asset_name(mat
)
752 # Using the cxr_graph_mapping as a reference, go through the shader
753 # graph and gather all $props from it.
755 def _graph_read( node_def
, node
=None, depth
=0 ):
759 def _variant_apply( val
):
762 if isinstance( val
, str ):
765 for shader_variant
in val
:
766 if shader_variant
[0] == mat
.cxr_data
.shader
:
767 return shader_variant
[1]
771 _graph_read
.extracted
= []
773 for node_idname
in node_def
:
774 for n
in mat
.node_tree
.nodes
:
775 if n
.bl_idname
== node_idname
:
776 node_def
= node_def
[node_idname
]
780 for link
in node_def
:
781 if isinstance( node_def
[link
], dict ):
782 inputt
= node
.inputs
[link
]
783 inputt_def
= node_def
[link
]
787 # look for definitions for the connected node type
788 con
= inputt
.links
[0].from_node
790 for node_idname
in inputt_def
:
791 if con
.bl_idname
== node_idname
:
792 con_def
= inputt_def
[ node_idname
]
793 _graph_read( con_def
, con
, depth
+1 )
795 # No definition found! :(
796 # TODO: Make a warning for this?
799 if "default" in inputt_def
:
800 prop
= _variant_apply( inputt_def
['default'] )
801 info
[prop
] = inputt
.default_value
803 prop
= _variant_apply( node_def
[link
] )
804 info
[prop
] = getattr(node
,link
)
806 _graph_read(cxr_graph_mapping
)
808 if "$basetexture" in info
:
809 export_res
= info
['$basetexture'].cxr_data
.export_res
810 info
['res'] = (export_res
[0], export_res
[1])
814 def mesh_cxr_format(obj
):
815 dgraph
= bpy
.context
.evaluated_depsgraph_get()
816 data
= obj
.evaluated_get(dgraph
).data
818 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
820 mesh
= cxr_input_mesh()
822 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
823 for i
, vert
in enumerate(data
.vertices
):
824 v
= obj
.matrix_world
@ vert
.co
825 vertex_data
[i
][0] = c_double(v
[0])
826 vertex_data
[i
][1] = c_double(v
[1])
827 vertex_data
[i
][2] = c_double(v
[2])
829 loop_data
= (cxr_input_loop
*len(data
.loops
))()
830 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
832 for i
, poly
in enumerate(data
.polygons
):
833 loop_start
= poly
.loop_start
834 loop_end
= poly
.loop_start
+ poly
.loop_total
835 for loop_index
in range(loop_start
, loop_end
):
836 loop
= data
.loops
[loop_index
]
837 loop_data
[loop_index
].index
= loop
.vertex_index
838 loop_data
[loop_index
].edge_index
= loop
.edge_index
841 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
842 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
843 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
845 loop_data
[loop_index
].uv
[0] = c_double(0.0)
846 loop_data
[loop_index
].uv
[1] = c_double(0.0)
847 center
= obj
.matrix_world
@ poly
.center
848 normal
= mtx_rot
@ poly
.normal
850 polygon_data
[i
].loop_start
= poly
.loop_start
851 polygon_data
[i
].loop_total
= poly
.loop_total
852 polygon_data
[i
].normal
[0] = normal
[0]
853 polygon_data
[i
].normal
[1] = normal
[1]
854 polygon_data
[i
].normal
[2] = normal
[2]
855 polygon_data
[i
].center
[0] = center
[0]
856 polygon_data
[i
].center
[1] = center
[1]
857 polygon_data
[i
].center
[2] = center
[2]
858 polygon_data
[i
].material_id
= poly
.material_index
860 edge_data
= (cxr_edge
*len(data
.edges
))()
862 for i
, edge
in enumerate(data
.edges
):
863 edge_data
[i
].i0
= edge
.vertices
[0]
864 edge_data
[i
].i1
= edge
.vertices
[1]
865 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
867 material_data
= (cxr_material
*len(obj
.material_slots
))()
869 for i
, ms
in enumerate(obj
.material_slots
):
870 inf
= material_info(ms
.material
)
871 material_data
[i
].res
[0] = inf
['res'][0]
872 material_data
[i
].res
[1] = inf
['res'][1]
873 material_data
[i
].vmt_path
= inf
['name'].encode('utf-8')
875 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
876 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
877 mesh
.loops
= cast(loop_data
,POINTER(cxr_input_loop
))
878 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
879 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
881 mesh
.poly_count
= len(data
.polygons
)
882 mesh
.vertex_count
= len(data
.vertices
)
883 mesh
.edge_count
= len(data
.edges
)
884 mesh
.loop_count
= len(data
.loops
)
885 mesh
.material_count
= len(obj
.material_slots
)
889 class CXR_WRITE_VMF(bpy
.types
.Operator
):
890 bl_idname
="convexer.write_vmf"
893 def execute(_
,context
):
895 libcxr_reset_debug_lines()
897 # Setup output and state
898 filepath
= bpy
.data
.filepath
899 directory
= os
.path
.dirname(filepath
)
900 settings
= context
.scene
.cxr_data
902 asset_dir
= F
"{directory}/bin"
903 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
904 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
906 os
.makedirs( asset_dir
, exist_ok
=True )
907 os
.makedirs( material_dir
, exist_ok
=True )
908 os
.makedirs( model_dir
, exist_ok
=True )
911 material_info
.references
= set()
912 libcxr_context_reset()
914 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
916 with
vdf_structure(output_vmf
) as m
:
917 print( F
"Write: {output_vmf}" )
919 m
.node('versioninfo')
920 m
.kv('editorversion','400')
921 m
.kv('editorbuild','8456')
922 m
.kv('mapversion','4')
923 m
.kv('formatversion','100')
930 m
.node('viewsettings')
931 m
.kv('bSnapToGrid','1')
932 m
.kv('bShowGrid','1')
933 m
.kv('bShowLogicalGrid','0')
934 m
.kv('nGridSpacing','64')
935 m
.kv('bShow3DGrid','0')
940 m
.kv('mapversion','1')
941 m
.kv('classname','worldspawn')
942 m
.kv('skyname','sky_csgo_night02b')
943 m
.kv('maxpropscreenwidth','-1')
944 m
.kv('detailvbsp','detail.vbsp')
945 m
.kv('detailmaterial','detail/detailsprites')
947 # Make sure all of our asset types have a unique ID
948 def _uid_prepare(objtype
):
954 if vs
.asset_id
in used_ids
:
957 id_max
= max(id_max
,vs
.asset_id
)
958 used_ids
+=[vs
.asset_id
]
959 for vs
in to_generate
:
963 _uid_prepare(bpy
.data
.materials
)
964 _uid_prepare(bpy
.data
.images
)
965 _uid_prepare(bpy
.data
.collections
)
967 # Export Brushes and displacement
968 def _collect(collection
,transform
):
969 if collection
.name
.startswith('.'):
972 if collection
.hide_render
:
975 if collection
.name
.startswith('mdl_'):
976 _collect
.heros
+= [(collection
,transform
)]
979 for obj
in collection
.objects
:
980 if obj
.hide_get(): continue
982 classname
= cxr_classname( obj
)
984 if classname
!= None:
985 _collect
.entities
+= [( obj
,transform
,classname
)]
986 elif obj
.type == 'MESH':
987 _collect
.geo
+= [(obj
,transform
)]
989 for c
in collection
.children
:
990 _collect( c
, transform
)
992 _collect
.a_models
= set()
993 _collect
.entities
= []
997 transform_main
= cxr_object_context( \
998 context
.scene
.cxr_data
.scale_factor
, 0.0 )
1000 transform_sky
= cxr_object_context( \
1001 context
.scene
.cxr_data
.skybox_scale_factor
, \
1002 context
.scene
.cxr_data
.skybox_offset
)
1004 if 'main' in bpy
.data
.collections
:
1005 _collect( bpy
.data
.collections
['main'], transform_main
)
1007 if 'skybox' in bpy
.data
.collections
:
1008 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1011 for brush
in _collect
.geo
:
1012 baked
= mesh_cxr_format( brush
[0] )
1013 libcxr_set_scale_factor( brush
[1].scale
)
1014 libcxr_set_offset( brush
[1].offset_z
)
1015 libcxr_convert_mesh_to_vmf(baked
,m
.fp
)
1020 for entity
in _collect
.entities
:
1025 m
.kv( 'classname', cls
)
1027 kvs
= cxr_entity_keyvalues( obj
, ctx
, cls
)
1030 if isinstance(kv
[2], list):
1031 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1032 else: m
.kv( kv
[0], str(kv
[2]) )
1034 if obj
.type == 'MESH':
1035 baked
= mesh_cxr_format( obj
)
1036 libcxr_set_scale_factor( ctx
.scale
)
1037 libcxr_set_offset( ctx
.offset_z
)
1038 libcxr_convert_mesh_to_vmf(baked
,m
.fp
)
1042 print( "[CONVEXER] Compile materials / textures" )
1044 for mat
in material_info
.references
:
1045 compile_material(mat
)
1047 print( "[CONVEXER] Compiling models" )
1049 libcxr_batch_debug_lines()
1054 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1055 bl_idname
="convexer.dev_test"
1056 bl_label
="Export development data"
1058 def execute(_
,context
):
1061 # Prepare input data
1062 mesh_src
= mesh_cxr_format(context
.active_object
)
1064 libcxr_reset_debug_lines()
1065 libcxr_write_test_data( pointer(mesh_src
) )
1066 libcxr_batch_debug_lines()
1071 class CXR_INTERFACE(bpy
.types
.Panel
):
1073 bl_idname
="SCENE_PT_convexer"
1074 bl_space_type
='PROPERTIES'
1075 bl_region_type
='WINDOW'
1078 def draw(_
,context
):
1079 _
.layout
.operator("convexer.reload")
1080 _
.layout
.operator("convexer.dev_test")
1081 _
.layout
.operator("convexer.write_vmf")
1083 settings
= context
.scene
.cxr_data
1085 _
.layout
.prop(settings
, "debug")
1086 _
.layout
.prop(settings
, "scale_factor")
1087 _
.layout
.prop(settings
, "lightmap_scale")
1088 _
.layout
.prop(settings
, "light_scale" )
1090 box
= _
.layout
.box()
1092 box
.prop(settings
, "project_name")
1093 box
.prop(settings
, "subdir")
1095 box
= _
.layout
.box()
1096 box
.operator("convexer.detect_compilers")
1097 box
.prop(settings
, "exe_studiomdl")
1098 box
.prop(settings
, "exe_vbsp")
1099 box
.prop(settings
, "exe_vvis")
1100 box
.prop(settings
, "exe_vrad")
1102 # COmpile image using NBVTF and hash it
1103 def compile_image(img
,flags
):
1107 name
= asset_name(img
)
1108 src_path
= bpy
.path
.abspath(img
.filepath
)
1110 dims
= img
.cxr_data
.export_res
1112 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888
,
1113 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1114 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1115 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1116 }[ img
.cxr_data
.fmt
]
1118 mipmap
= img
.cxr_data
.mipmap
1119 lod
= img
.cxr_data
.lod
1120 clamp
= img
.cxr_data
.clamp
1122 userflag_hash
= F
"{mipmap}.{lod}.{clamp}"
1123 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1124 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}"
1126 if img
.cxr_data
.last_hash
!= comphash
:
1127 print( F
"Texture update: {img.filepath}" )
1129 src
= src_path
.encode('utf-8')
1130 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1134 # texture setting flags
1135 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1137 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1138 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1140 if libnbvtf_convert(src
,dims
[0],dims
[1],mipmap
,fmt
,flags_full
,dst
):
1141 img
.cxr_data
.last_hash
= comphash
1145 def compile_material(mat
):
1146 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1148 info
= material_info(mat
)
1149 properties
= mat
.cxr_data
1153 def _mlayer( layer
):
1154 nonlocal properties
, props
1157 if isinstance(layer
[decl
],dict): # $property definition
1159 ptype
= pdef
['type']
1165 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1168 # Group expansion (does it have subdefinitions?)
1170 if isinstance(pdef
[ch
],dict):
1179 if ptype
== 'intrinsic':
1183 prop
= getattr(properties
,decl
)
1184 default
= pdef
['default']
1186 if not isinstance(prop
,str) and \
1187 not isinstance(prop
,bpy
.types
.Image
) and \
1188 hasattr(prop
,'__getitem__'):
1189 prop
= tuple([p
for p
in prop
])
1193 props
+= [(decl
,pdef
,prop
)]
1198 if expandview
: _mlayer(pdef
)
1200 _mlayer( cxr_shader_params
)
1202 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1203 vmt
.node( properties
.shader
)
1204 vmt
.put( "// Convexer export\n" )
1214 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1217 if isinstance(prop
,bpy
.types
.Image
):
1220 flags
= pdef
['flags']
1221 vmt
.kv( decl
,compile_image(prop
,flags
))
1223 elif isinstance(prop
,bool):
1224 vmt
.kv( decl
, '1' if prop
else '0' )
1225 elif isinstance(prop
,str):
1226 vmt
.kv( decl
, prop
)
1227 elif isinstance(prop
,float) or isinstance(prop
,int):
1228 vmt
.kv( decl
, _numeric(prop
) )
1229 elif isinstance(prop
,tuple):
1230 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1232 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1236 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1237 bl_label
="VMT Properties"
1238 bl_idname
="SCENE_PT_convexer_vmt"
1239 bl_space_type
='PROPERTIES'
1240 bl_region_type
='WINDOW'
1241 bl_context
="material"
1243 def draw(_
,context
):
1244 active_object
= bpy
.context
.active_object
1245 if active_object
== None: return
1247 active_material
= active_object
.active_material
1248 if active_material
== None: return
1250 properties
= active_material
.cxr_data
1251 info
= material_info( active_material
)
1253 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1254 _
.layout
.prop( properties
, "shader" )
1257 _
.layout
.label(text
=F
"{xk}:={info[xk]}")
1259 def _mtex( name
, img
, uiParent
):
1262 box
= uiParent
.box()
1263 box
.label( text
=F
'{name} "{img.filepath}"' )
1265 if ((x
& (x
- 1)) == 0):
1268 closest_diff
= 10000000
1270 dist
= abs((1 << i
)-x
)
1271 if dist
< closest_diff
:
1276 return 1 << (closest
+1)
1278 return 1 << (closest
-1)
1283 row
.prop( img
.cxr_data
, "export_res" )
1284 row
.prop( img
.cxr_data
, "fmt" )
1287 row
.prop( img
.cxr_data
, "mipmap" )
1288 row
.prop( img
.cxr_data
, "lod" )
1289 row
.prop( img
.cxr_data
, "clamp" )
1291 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1292 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1294 def _mview( layer
, uiParent
):
1298 if isinstance(layer
[decl
],dict): # $property definition
1300 ptype
= pdef
['type']
1306 if ('shaders' in pdef
) and \
1307 (properties
.shader
not in pdef
['shaders']):
1310 if ptype
== 'intrinsic':
1311 if decl
not in info
:
1316 if isinstance(pdef
[ch
],dict):
1317 if ptype
== 'ui' or ptype
== 'intrinsic':
1319 elif getattr(properties
,decl
) == pdef
['default']:
1322 thisnode
= uiParent
.box()
1326 thisnode
.label( text
=decl
)
1327 elif ptype
== 'intrinsic':
1328 if isinstance(info
[decl
], bpy
.types
.Image
):
1329 _mtex( decl
, info
[decl
], thisnode
)
1331 # hidden intrinsic value.
1332 # Means its a float array or something not an image
1333 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
1335 thisnode
.prop(properties
,decl
)
1336 if expandview
: _mview(pdef
,thisnode
)
1338 _mview( cxr_shader_params
, _
.layout
)
1340 def cxr_entity_changeclass(_
,context
):
1341 active_object
= context
.active_object
1343 # Create ID properties
1345 classname
= active_object
.cxr_data
.classname
1347 if classname
in cxr_entities
:
1348 entdef
= cxr_entities
[classname
]
1350 kvs
= entdef
['keyvalues']
1351 if callable(kvs
): kvs
= kvs(active_object
)
1357 if callable(kv
) or not isinstance(kv
,dict): continue
1359 if key
not in active_object
:
1360 active_object
[key
] = kv
['default']
1361 id_prop
= active_object
.id_properties_ui(key
)
1362 id_prop
.update(default
=kv
['default'])
1364 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
1365 bl_label
="Entity Config"
1366 bl_idname
="SCENE_PT_convexer_entity"
1367 bl_space_type
='PROPERTIES'
1368 bl_region_type
='WINDOW'
1371 def draw(_
,context
):
1372 active_object
= bpy
.context
.active_object
1374 if active_object
== None: return
1376 default_context
= cxr_object_context( \
1377 bpy
.context
.scene
.cxr_data
.scale_factor
, 0.0 )
1379 ecn
= cxr_intrinsic_classname( active_object
)
1380 classname
= cxr_custom_class( active_object
)
1383 if active_object
.type == 'MESH':
1384 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
1385 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
1387 if classname
== 'NONE':
1390 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
1391 _
.layout
.enabled
=False
1394 kvs
= cxr_entity_keyvalues( active_object
, default_context
, classname
)
1398 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
1400 row
= _
.layout
.row()
1402 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
1404 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
1406 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
1407 bl_label
= "Source Settings"
1408 bl_idname
= "LIGHT_PT_cxr"
1409 bl_space_type
= 'PROPERTIES'
1410 bl_region_type
= 'WINDOW'
1413 def draw(self
, context
):
1414 layout
= self
.layout
1415 scene
= context
.scene
1417 active_object
= bpy
.context
.active_object
1418 if active_object
== None: return
1420 if active_object
.type == 'LIGHT' or \
1421 active_object
.type == 'LIGHT_PROBE':
1423 properties
= active_object
.data
.cxr_data
1425 if active_object
.type == 'LIGHT':
1426 layout
.prop( properties
, "realtime" )
1427 elif active_object
.type == 'LIGHT_PROBE':
1428 layout
.prop( properties
, "size" )
1430 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
1431 export_res
: bpy
.props
.IntVectorProperty(
1433 description
="Texture Export Resolution",
1439 fmt
: bpy
.props
.EnumProperty(
1442 ('DXT1', "DXT1", "BC1 compressed", '', 0),
1443 ('DXT5', "DXT5", "BC3 compressed", '', 1),
1444 ('RGB', "RGB", "Uncompressed", '', 2),
1445 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
1447 description
="Image format",
1450 last_hash
: bpy
.props
.StringProperty( name
="" )
1451 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1453 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
1454 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
1455 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
1457 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
1458 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
1460 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
1461 size
: bpy
.props
.EnumProperty(
1464 ('1',"1x1",'','',0),
1465 ('2',"2x2",'','',1),
1466 ('3',"4x4",'','',2),
1467 ('4',"8x8",'','',3),
1468 ('5',"16x16",'','',4),
1469 ('6',"32x32",'','',5),
1470 ('7',"64x64",'','',6),
1471 ('8',"128x128",'','',7),
1472 ('9',"256x256",'','',8)
1474 description
="Texture resolution",
1477 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
1478 entity
: bpy
.props
.BoolProperty(name
="")
1480 enum_pointents
= [('NONE',"None","")]
1481 enum_brushents
= [('NONE',"None","")]
1483 for classname
in cxr_entities
:
1484 entdef
= cxr_entities
[classname
]
1485 if 'allow' in entdef
:
1486 itm
= [(classname
, classname
, "")]
1487 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
1488 else: enum_brushents
+= itm
1490 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
1491 update
=cxr_entity_changeclass
, default
='NONE' )
1493 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
1494 update
=cxr_entity_changeclass
, default
='NONE' )
1496 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
1497 last_hash
: bpy
.props
.StringProperty( name
="" )
1498 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
1500 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
1501 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
1502 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
1504 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
1505 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
1506 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
1507 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
1508 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
1509 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
1510 opt_vrad
: bpy
.props
.StringProperty( name
="args" )
1512 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
1513 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
1514 default
=32.0,min=1.0)
1515 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
1516 default
=1.0,min=0.01)
1518 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
1519 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
1520 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
1522 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
1525 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1526 bl_idname
="convexer.detect_compilers"
1527 bl_label
="Find compilers"
1529 def execute(self
,context
):
1530 scene
= context
.scene
1531 settings
= scene
.cxr_data
1532 subdir
= settings
.subdir
1534 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1535 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1536 if os
.path
.exists(searchpath
):
1537 settings
[F
'exe_{exename}'] = searchpath
1541 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
1542 CXR_WRITE_VMF
, CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
1543 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
1544 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
1545 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
]
1548 global debug_draw_handler
, vmt_param_dynamic_class
1551 bpy
.utils
.register_class(c
)
1553 # Build dynamic VMT properties class defined by cxr_shader_params
1554 annotations_dict
= {}
1556 def _dvmt_propogate(layer
):
1557 nonlocal annotations_dict
1560 if isinstance(layer
[decl
],dict): # $property definition
1564 if pdef
['type'] == 'bool':
1565 prop
= bpy
.props
.BoolProperty(\
1566 name
= pdef
['name'],\
1567 default
= pdef
['default'])
1569 elif pdef
['type'] == 'float':
1570 prop
= bpy
.props
.FloatProperty(\
1571 name
= pdef
['name'],\
1572 default
= pdef
['default'])
1574 elif pdef
['type'] == 'vector':
1575 if 'subtype' in pdef
:
1576 prop
= bpy
.props
.FloatVectorProperty(\
1577 name
= pdef
['name'],\
1578 subtype
= pdef
['subtype'],\
1579 default
= pdef
['default'],\
1580 size
= len(pdef
['default']))
1582 prop
= bpy
.props
.FloatVectorProperty(\
1583 name
= pdef
['name'],\
1584 default
= pdef
['default'],\
1585 size
= len(pdef
['default']))
1587 elif pdef
['type'] == 'string':
1588 prop
= bpy
.props
.StringProperty(\
1589 name
= pdef
['name'],\
1590 default
= pdef
['default'])
1592 elif pdef
['type'] == 'enum':
1593 prop
= bpy
.props
.EnumProperty(\
1594 name
= pdef
['name'],\
1595 items
= pdef
['items'],\
1596 default
= pdef
['default'])
1599 annotations_dict
[decl
] = prop
1601 # Recurse into sub-definitions
1602 _dvmt_propogate(pdef
)
1604 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
1607 cxr_shaders
[_
]["name"],\
1608 '') for _
in cxr_shaders
],\
1609 default
= next(iter(cxr_shaders
)))
1611 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
1614 _dvmt_propogate( cxr_shader_params
)
1615 vmt_param_dynamic_class
= type(
1617 (bpy
.types
.PropertyGroup
,),{
1618 "__annotations__": annotations_dict
1622 bpy
.utils
.register_class( vmt_param_dynamic_class
)
1625 bpy
.types
.Material
.cxr_data
= \
1626 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
1627 bpy
.types
.Image
.cxr_data
= \
1628 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
1629 bpy
.types
.Object
.cxr_data
= \
1630 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
1631 bpy
.types
.Collection
.cxr_data
= \
1632 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
1633 bpy
.types
.Light
.cxr_data
= \
1634 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
1635 bpy
.types
.LightProbe
.cxr_data
= \
1636 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
1637 bpy
.types
.Scene
.cxr_data
= \
1638 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
1640 # CXR Scene settings
1643 debug_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
1644 cxr_draw
,(),'WINDOW','POST_VIEW')
1646 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
1647 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
1650 global debug_draw_handler
, vmt_param_dynamic_class
1652 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
1654 bpy
.utils
.unregister_class(c
)
1656 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
1657 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
1659 bpy
.types
.SpaceView3D
.draw_handler_remove(debug_draw_handler
,'WINDOW')