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
),
122 class cxr_material(Structure
):
123 _fields_
= [("res",c_int32
* 2),
124 ("vmt_path",c_char_p
)]
126 class cxr_input_mesh(Structure
):
127 _fields_
= [("vertices",POINTER(c_double
* 3)),
128 ("edges",POINTER(cxr_edge
)),
129 ("loops",POINTER(cxr_input_loop
)),
130 ("polys",POINTER(cxr_polygon
)),
131 ("materials",POINTER(cxr_material
)),
133 ("poly_count",c_int32
),
134 ("vertex_count",c_int32
),
135 ("edge_count",c_int32
),
136 ("loop_count",c_int32
),
137 ("material_count",c_int32
)]
139 class cxr_output_mesh(Structure
):
142 def libcxr_log_callback(logStr
):
143 print( F
"{logStr.decode('utf-8')}",end
='' )
145 debug_lines_positions
= None
146 debug_lines_colours
= None
148 def libcxr_reset_debug_lines():
149 global debug_lines_positions
150 global debug_lines_colours
152 debug_lines_positions
= []
153 debug_lines_colours
= []
155 def libcxr_batch_debug_lines():
156 global debug_lines_positions
157 global debug_lines_colours
158 global debug_gpu_lines
159 global debug_gpu_shader
161 debug_gpu_lines
= batch_for_shader(\
162 debug_gpu_shader
, 'LINES',\
163 { "pos": debug_lines_positions
, "color": debug_lines_colours
})
166 def cxr_on_load(dummy
):
167 libcxr_reset_debug_lines()
168 libcxr_batch_debug_lines()
171 def cxr_dgraph_update(scene
,dgraph
):
173 print( F
"Hallo {time.time()}" )
175 def libcxr_line_callback(p0
,p1
,colour
):
176 global debug_lines_positions
177 global debug_lines_colours
178 debug_lines_positions
+= [(p0
[0],p0
[1],p0
[2])]
179 debug_lines_positions
+= [(p1
[0],p1
[1],p1
[2])]
180 debug_lines_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
181 debug_lines_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
184 global debug_gpu_lines
185 global debug_gpu_shader
187 gpu
.state
.blend_set('ALPHA')
188 if debug_gpu_lines
!= None:
189 debug_gpu_lines
.draw(debug_gpu_shader
)
191 class CXR_RELOAD(bpy
.types
.Operator
):
192 bl_idname
="convexer.reload"
193 bl_label
="Reload convexer"
195 def execute(_
,context
):
196 global libcxr
, libnbvtf
, libnbvtf_convert
199 libnbvtf
= cdll
.LoadLibrary( os
.path
.dirname(__file__
)+'/libnbvtf.so')
200 libnbvtf_convert
= libnbvtf
.nbvtf_convert
201 libnbvtf_convert
.argtypes
= [\
209 libnbvtf_convert
.restype
= c_int32
212 _handle
= libcxr
._handle
214 # TODO: Find a propper way to do this
215 for i
in range(10): libc_dlclose( _handle
)
219 libcxr
= cdll
.LoadLibrary( os
.path
.dirname(__file__
)+'/libcxr.so')
221 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
222 print( F
"libcxr build time: {build_time.value}" )
225 global libcxr_decompose
226 global libcxr_convert_mesh_to_vmf
228 libcxr_decompose
= libcxr
.cxr_decompose
229 libcxr_decompose
.argtypes
= [\
230 POINTER(cxr_input_mesh
)
232 libcxr_decompose
.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 awful.
610 # Blender's default has a much more 'accurate' look
611 # They appear correct when using linear scale.
612 kvs
['_linear_attn'] = 1.0
614 elif obj
.data
.type == 'POINT':
615 kvs
['_quadratic_attn'] = 1.0
616 kvs
['_linear_attn'] = 0.0
618 elif obj
.data
.type == 'SUN':
623 def ent_cubemap(obj
,context
):
624 return ent_baseclass([ent_origin
],\
625 {"cubemapsize": obj
.data
.cxr_data
.size
})
628 "info_player_counterterrorist":
632 "keyvalues": ent_baseclass([ent_transform
],\
634 "priority": {"type": "int", "default": 0 },
635 "enabled": {"type": "int", "default": 1 },
638 "light": { "keyvalues": ent_lights
},
639 "light_spot": { "keyvalues": ent_lights
},
641 "env_cubemap": { "keyvalues": ent_cubemap
},
649 "TeamNum": {"type": "int", "default": 0 }
654 def cxr_intrinsic_classname(obj
):
655 if obj
.type == 'LIGHT':
657 'SPOT': "light_spot",
659 'SUN': "light_directional" }[ obj
.data
.type ]
661 elif obj
.type == 'LIGHT_PROBE':
663 elif obj
.type == 'EMPTY':
669 def cxr_custom_class(obj
):
670 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
671 else: custom_class
= obj
.cxr_data
.classname
675 def cxr_classname(obj
):
676 intr
= cxr_intrinsic_classname(obj
)
677 if intr
!= None: return intr
679 custom_class
= cxr_custom_class(obj
)
680 if custom_class
!= 'NONE':
686 # intinsic: (k, False, value)
687 # property: (k, True, value or default)
691 def cxr_entity_keyvalues(obj
,context
,classname
):
692 if classname
not in cxr_entities
: return None
696 entdef
= cxr_entities
[classname
]
697 kvs
= entdef
['keyvalues']
699 if callable(kvs
): kvs
= kvs(obj
, context
)
706 if isinstance(kv
,dict):
708 value
= obj
[ F
"cxrkv_{k}" ]
711 value
= kv(obj
,context
)
713 if isinstance(value
,mathutils
.Vector
):
714 value
= [_
for _
in value
]
716 result
+= [(k
, isprop
, value
)]
720 def material_info(mat
):
722 info
['res'] = (512,512)
723 info
['name'] = 'tools/toolsnodraw'
725 if mat
== None or mat
.use_nodes
== False:
729 if mat
.cxr_data
.shader
== 'Builtin':
730 info
['name'] = mat
.name
733 if not hasattr(material_info
,'references'):
734 material_info
.references
= set()
737 material_info
.references
.add(mat
)
738 info
['name'] = asset_name(mat
)
740 # Using the cxr_graph_mapping as a reference, go through the shader
741 # graph and gather all $props from it.
743 def _graph_read( node_def
, node
=None, depth
=0 ):
747 def _variant_apply( val
):
750 if isinstance( val
, str ):
753 for shader_variant
in val
:
754 if shader_variant
[0] == mat
.cxr_data
.shader
:
755 return shader_variant
[1]
759 _graph_read
.extracted
= []
761 for node_idname
in node_def
:
762 for n
in mat
.node_tree
.nodes
:
763 if n
.bl_idname
== node_idname
:
764 node_def
= node_def
[node_idname
]
768 for link
in node_def
:
769 if isinstance( node_def
[link
], dict ):
770 inputt
= node
.inputs
[link
]
771 inputt_def
= node_def
[link
]
775 # look for definitions for the connected node type
776 con
= inputt
.links
[0].from_node
778 for node_idname
in inputt_def
:
779 if con
.bl_idname
== node_idname
:
780 con_def
= inputt_def
[ node_idname
]
781 _graph_read( con_def
, con
, depth
+1 )
783 # No definition found! :(
784 # TODO: Make a warning for this?
787 if "default" in inputt_def
:
788 prop
= _variant_apply( inputt_def
['default'] )
789 info
[prop
] = inputt
.default_value
791 prop
= _variant_apply( node_def
[link
] )
792 info
[prop
] = getattr(node
,link
)
794 _graph_read(cxr_graph_mapping
)
796 if "$basetexture" in info
:
797 export_res
= info
['$basetexture'].cxr_data
.export_res
798 info
['res'] = (export_res
[0], export_res
[1])
802 def mesh_cxr_format(obj
):
803 dgraph
= bpy
.context
.evaluated_depsgraph_get()
804 data
= obj
.evaluated_get(dgraph
).data
806 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
808 mesh
= cxr_input_mesh()
810 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
811 for i
, vert
in enumerate(data
.vertices
):
812 v
= obj
.matrix_world
@ vert
.co
813 vertex_data
[i
][0] = c_double(v
[0])
814 vertex_data
[i
][1] = c_double(v
[1])
815 vertex_data
[i
][2] = c_double(v
[2])
817 loop_data
= (cxr_input_loop
*len(data
.loops
))()
818 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
820 for i
, poly
in enumerate(data
.polygons
):
821 loop_start
= poly
.loop_start
822 loop_end
= poly
.loop_start
+ poly
.loop_total
823 for loop_index
in range(loop_start
, loop_end
):
824 loop
= data
.loops
[loop_index
]
825 loop_data
[loop_index
].index
= loop
.vertex_index
826 loop_data
[loop_index
].edge_index
= loop
.edge_index
829 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
830 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
831 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
833 loop_data
[loop_index
].uv
[0] = c_double(0.0)
834 loop_data
[loop_index
].uv
[1] = c_double(0.0)
835 center
= obj
.matrix_world
@ poly
.center
836 normal
= mtx_rot
@ poly
.normal
838 polygon_data
[i
].loop_start
= poly
.loop_start
839 polygon_data
[i
].loop_total
= poly
.loop_total
840 polygon_data
[i
].normal
[0] = normal
[0]
841 polygon_data
[i
].normal
[1] = normal
[1]
842 polygon_data
[i
].normal
[2] = normal
[2]
843 polygon_data
[i
].center
[0] = center
[0]
844 polygon_data
[i
].center
[1] = center
[1]
845 polygon_data
[i
].center
[2] = center
[2]
846 polygon_data
[i
].material_id
= poly
.material_index
848 edge_data
= (cxr_edge
*len(data
.edges
))()
850 for i
, edge
in enumerate(data
.edges
):
851 edge_data
[i
].i0
= edge
.vertices
[0]
852 edge_data
[i
].i1
= edge
.vertices
[1]
854 material_data
= (cxr_material
*len(obj
.material_slots
))()
856 for i
, ms
in enumerate(obj
.material_slots
):
857 inf
= material_info(ms
.material
)
858 material_data
[i
].res
[0] = inf
['res'][0]
859 material_data
[i
].res
[1] = inf
['res'][1]
860 material_data
[i
].vmt_path
= inf
['name'].encode('utf-8')
862 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
863 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
864 mesh
.loops
= cast(loop_data
,POINTER(cxr_input_loop
))
865 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
866 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
868 mesh
.poly_count
= len(data
.polygons
)
869 mesh
.vertex_count
= len(data
.vertices
)
870 mesh
.edge_count
= len(data
.edges
)
871 mesh
.loop_count
= len(data
.loops
)
872 mesh
.material_count
= len(obj
.material_slots
)
876 class CXR_WRITE_VMF(bpy
.types
.Operator
):
877 bl_idname
="convexer.write_vmf"
880 def execute(_
,context
):
882 libcxr_reset_debug_lines()
884 # Setup output and state
885 filepath
= bpy
.data
.filepath
886 directory
= os
.path
.dirname(filepath
)
887 settings
= context
.scene
.cxr_data
889 asset_dir
= F
"{directory}/bin"
890 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
891 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
893 os
.makedirs( asset_dir
, exist_ok
=True )
894 os
.makedirs( material_dir
, exist_ok
=True )
895 os
.makedirs( model_dir
, exist_ok
=True )
898 material_info
.references
= set()
899 libcxr_context_reset()
901 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
903 with
vdf_structure(output_vmf
) as m
:
904 print( F
"Write: {output_vmf}" )
906 m
.node('versioninfo')
907 m
.kv('editorversion','400')
908 m
.kv('editorbuild','8456')
909 m
.kv('mapversion','4')
910 m
.kv('formatversion','100')
917 m
.node('viewsettings')
918 m
.kv('bSnapToGrid','1')
919 m
.kv('bShowGrid','1')
920 m
.kv('bShowLogicalGrid','0')
921 m
.kv('nGridSpacing','64')
922 m
.kv('bShow3DGrid','0')
927 m
.kv('mapversion','1')
928 m
.kv('classname','worldspawn')
929 m
.kv('skyname','sky_csgo_night02b')
930 m
.kv('maxpropscreenwidth','-1')
931 m
.kv('detailvbsp','detail.vbsp')
932 m
.kv('detailmaterial','detail/detailsprites')
934 # Make sure all of our asset types have a unique ID
935 def _uid_prepare(objtype
):
941 if vs
.asset_id
in used_ids
:
944 id_max
= max(id_max
,vs
.asset_id
)
945 used_ids
+=[vs
.asset_id
]
946 for vs
in to_generate
:
950 _uid_prepare(bpy
.data
.materials
)
951 _uid_prepare(bpy
.data
.images
)
952 _uid_prepare(bpy
.data
.collections
)
954 # Export Brushes and displacement
955 def _collect(collection
,transform
):
956 if collection
.name
.startswith('.'):
959 if collection
.name
.startswith('mdl_'):
960 _collect
.heros
+= [(collection
,transform
)]
963 for obj
in collection
.objects
:
964 classname
= cxr_classname( obj
)
966 if classname
!= None:
967 _collect
.entities
+= [( obj
,transform
,classname
)]
968 elif obj
.type == 'MESH':
969 _collect
.geo
+= [(obj
,transform
)]
971 for c
in collection
.children
:
972 _collect( c
, transform
)
974 _collect
.a_models
= set()
975 _collect
.entities
= []
978 transform_main
= cxr_object_context( context
.scene
.cxr_data
.scale_factor
, 0.0 )
979 transform_sky
= cxr_object_context( context
.scene
.cxr_data
.skybox_scale_factor
, \
980 context
.scene
.cxr_data
.skybox_offset
)
982 if 'main' in bpy
.data
.collections
:
983 _collect( bpy
.data
.collections
['main'], transform_main
)
985 if 'skybox' in bpy
.data
.collections
:
986 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
989 for brush
in _collect
.geo
:
990 baked
= mesh_cxr_format( brush
[0] )
991 libcxr_set_scale_factor( brush
[1].scale
)
992 libcxr_set_offset( brush
[1].offset_z
)
993 libcxr_convert_mesh_to_vmf(baked
,m
.fp
)
998 for entity
in _collect
.entities
:
1003 m
.kv( 'classname', cls
)
1005 kvs
= cxr_entity_keyvalues( obj
, ctx
, cls
)
1008 if isinstance(kv
[2], list):
1009 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1010 else: m
.kv( kv
[0], str(kv
[2]) )
1012 if obj
.type == 'MESH':
1013 baked
= mesh_cxr_format( obj
)
1014 libcxr_set_scale_factor( ctx
.scale
)
1015 libcxr_set_offset( ctx
.offset_z
)
1016 libcxr_convert_mesh_to_vmf(baked
,m
.fp
)
1020 print( "[CONVEXER] Compile materials / textures" )
1022 for mat
in material_info
.references
:
1023 compile_material(mat
)
1025 print( "[CONVEXER] Compiling models" )
1027 libcxr_batch_debug_lines()
1032 class CXR_DECOMPOSE_SOLID(bpy
.types
.Operator
):
1033 bl_idname
="convexer.decompose_solid"
1034 bl_label
="Decompose Solid"
1036 def execute(_
,context
):
1039 # Prepare input data
1040 mesh_src
= mesh_cxr_format(context
.active_object
)
1042 libcxr_reset_debug_lines()
1043 libcxr_decompose( pointer(mesh_src
) )
1044 libcxr_batch_debug_lines()
1049 class CXR_INTERFACE(bpy
.types
.Panel
):
1051 bl_idname
="SCENE_PT_convexer"
1052 bl_space_type
='PROPERTIES'
1053 bl_region_type
='WINDOW'
1056 def draw(_
,context
):
1057 _
.layout
.operator("convexer.reload")
1058 _
.layout
.operator("convexer.decompose_solid")
1059 _
.layout
.operator("convexer.write_vmf")
1061 settings
= context
.scene
.cxr_data
1063 _
.layout
.prop(settings
, "debug")
1064 _
.layout
.prop(settings
, "scale_factor")
1065 _
.layout
.prop(settings
, "lightmap_scale")
1066 _
.layout
.prop(settings
, "light_scale" )
1068 box
= _
.layout
.box()
1070 box
.prop(settings
, "project_name")
1071 box
.prop(settings
, "subdir")
1073 box
= _
.layout
.box()
1074 box
.operator("convexer.detect_compilers")
1075 box
.prop(settings
, "exe_studiomdl")
1076 box
.prop(settings
, "exe_vbsp")
1077 box
.prop(settings
, "exe_vvis")
1078 box
.prop(settings
, "exe_vrad")
1080 # COmpile image using NBVTF and hash it
1081 def compile_image(img
,flags
):
1085 name
= asset_name(img
)
1086 src_path
= bpy
.path
.abspath(img
.filepath
)
1088 dims
= img
.cxr_data
.export_res
1090 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888
,
1091 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1092 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1093 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1094 }[ img
.cxr_data
.fmt
]
1096 mipmap
= img
.cxr_data
.mipmap
1097 lod
= img
.cxr_data
.lod
1098 clamp
= img
.cxr_data
.clamp
1100 userflag_hash
= F
"{mipmap}.{lod}.{clamp}"
1101 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1102 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}"
1104 if img
.cxr_data
.last_hash
!= comphash
:
1105 print( F
"Texture update: {img.filepath}" )
1107 src
= src_path
.encode('utf-8')
1108 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1112 # texture setting flags
1113 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1115 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1116 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1118 if libnbvtf_convert(src
,dims
[0],dims
[1],mipmap
,fmt
,flags_full
,dst
):
1119 img
.cxr_data
.last_hash
= comphash
1123 def compile_material(mat
):
1124 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1126 info
= material_info(mat
)
1127 properties
= mat
.cxr_data
1131 def _mlayer( layer
):
1132 nonlocal properties
, props
1135 if isinstance(layer
[decl
],dict): # $property definition
1137 ptype
= pdef
['type']
1143 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1146 # Group expansion (does it have subdefinitions?)
1148 if isinstance(pdef
[ch
],dict):
1157 if ptype
== 'intrinsic':
1161 prop
= getattr(properties
,decl
)
1162 default
= pdef
['default']
1164 if not isinstance(prop
,str) and \
1165 not isinstance(prop
,bpy
.types
.Image
) and \
1166 hasattr(prop
,'__getitem__'):
1167 prop
= tuple([p
for p
in prop
])
1171 props
+= [(decl
,pdef
,prop
)]
1176 if expandview
: _mlayer(pdef
)
1178 _mlayer( cxr_shader_params
)
1180 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1181 vmt
.node( properties
.shader
)
1182 vmt
.put( "// Convexer export\n" )
1192 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1195 if isinstance(prop
,bpy
.types
.Image
):
1198 flags
= pdef
['flags']
1199 vmt
.kv( decl
,compile_image(prop
,flags
))
1201 elif isinstance(prop
,bool):
1202 vmt
.kv( decl
, '1' if prop
else '0' )
1203 elif isinstance(prop
,str):
1204 vmt
.kv( decl
, prop
)
1205 elif isinstance(prop
,float) or isinstance(prop
,int):
1206 vmt
.kv( decl
, _numeric(prop
) )
1207 elif isinstance(prop
,tuple):
1208 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1210 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1214 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1215 bl_label
="VMT Properties"
1216 bl_idname
="SCENE_PT_convexer_vmt"
1217 bl_space_type
='PROPERTIES'
1218 bl_region_type
='WINDOW'
1219 bl_context
="material"
1221 def draw(_
,context
):
1222 active_object
= bpy
.context
.active_object
1223 if active_object
== None: return
1225 active_material
= active_object
.active_material
1226 if active_material
== None: return
1228 properties
= active_material
.cxr_data
1229 info
= material_info( active_material
)
1231 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1232 _
.layout
.prop( properties
, "shader" )
1235 _
.layout
.label(text
=F
"{xk}:={info[xk]}")
1237 def _mtex( name
, img
, uiParent
):
1240 box
= uiParent
.box()
1241 box
.label( text
=F
'{name} "{img.filepath}"' )
1243 if ((x
& (x
- 1)) == 0):
1246 closest_diff
= 10000000
1248 dist
= abs((1 << i
)-x
)
1249 if dist
< closest_diff
:
1254 return 1 << (closest
+1)
1256 return 1 << (closest
-1)
1261 row
.prop( img
.cxr_data
, "export_res" )
1262 row
.prop( img
.cxr_data
, "fmt" )
1265 row
.prop( img
.cxr_data
, "mipmap" )
1266 row
.prop( img
.cxr_data
, "lod" )
1267 row
.prop( img
.cxr_data
, "clamp" )
1269 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1270 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1272 def _mview( layer
, uiParent
):
1276 if isinstance(layer
[decl
],dict): # $property definition
1278 ptype
= pdef
['type']
1284 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1287 if ptype
== 'intrinsic':
1288 if decl
not in info
:
1293 if isinstance(pdef
[ch
],dict):
1294 if ptype
== 'ui' or ptype
== 'intrinsic':
1296 elif getattr(properties
,decl
) == pdef
['default']:
1299 thisnode
= uiParent
.box()
1303 thisnode
.label( text
=decl
)
1304 elif ptype
== 'intrinsic':
1305 if isinstance(info
[decl
], bpy
.types
.Image
):
1306 _mtex( decl
, info
[decl
], thisnode
)
1308 # hidden intrinsic value.
1309 # Means its a float array or something not an image
1310 thisnode
.label( text
=F
"-- hidden intrinsic '{decl}' --" )
1312 thisnode
.prop(properties
,decl
)
1313 if expandview
: _mview(pdef
,thisnode
)
1315 _mview( cxr_shader_params
, _
.layout
)
1317 def cxr_entity_changeclass(_
,context
):
1318 active_object
= context
.active_object
1320 # Create ID properties
1322 classname
= active_object
.cxr_data
.classname
1324 if classname
in cxr_entities
:
1325 entdef
= cxr_entities
[classname
]
1327 kvs
= entdef
['keyvalues']
1328 if callable(kvs
): kvs
= kvs(active_object
)
1334 if callable(kv
) or not isinstance(kv
,dict): continue
1336 if key
not in active_object
:
1337 active_object
[key
] = kv
['default']
1338 id_prop
= active_object
.id_properties_ui(key
)
1339 id_prop
.update(default
=kv
['default'])
1341 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
1342 bl_label
="Entity Config"
1343 bl_idname
="SCENE_PT_convexer_entity"
1344 bl_space_type
='PROPERTIES'
1345 bl_region_type
='WINDOW'
1348 def draw(_
,context
):
1349 active_object
= bpy
.context
.active_object
1351 if active_object
== None: return
1353 default_context
= cxr_object_context( bpy
.context
.scene
.cxr_data
.scale_factor
, 0.0 )
1354 ecn
= cxr_intrinsic_classname( active_object
)
1355 classname
= cxr_custom_class( active_object
)
1358 if active_object
.type == 'MESH': _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
1359 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
1361 if classname
== 'NONE':
1364 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
1365 _
.layout
.enabled
=False
1368 kvs
= cxr_entity_keyvalues( active_object
, default_context
, classname
)
1372 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
1374 row
= _
.layout
.row()
1376 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
1378 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
1380 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
1381 bl_label
= "Source Settings"
1382 bl_idname
= "LIGHT_PT_cxr"
1383 bl_space_type
= 'PROPERTIES'
1384 bl_region_type
= 'WINDOW'
1387 def draw(self
, context
):
1388 layout
= self
.layout
1389 scene
= context
.scene
1391 active_object
= bpy
.context
.active_object
1392 if active_object
== None: return
1394 if active_object
.type == 'LIGHT' or \
1395 active_object
.type == 'LIGHT_PROBE':
1397 properties
= active_object
.data
.cxr_data
1399 if active_object
.type == 'LIGHT':
1400 layout
.prop( properties
, "realtime" )
1401 elif active_object
.type == 'LIGHT_PROBE':
1402 layout
.prop( properties
, "size" )
1404 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
1405 export_res
: bpy
.props
.IntVectorProperty(
1407 description
="Texture Export Resolution",
1413 fmt
: bpy
.props
.EnumProperty(
1416 ('DXT1', "DXT1", "BC1 compressed", '', 0),
1417 ('DXT5', "DXT5", "BC3 compressed", '', 1),
1418 ('RGB', "RGB", "Uncompressed", '', 2),
1419 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
1421 description
="Image format",
1424 last_hash
: bpy
.props
.StringProperty( name
="" )
1425 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1427 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
1428 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
1429 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
1431 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
1432 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
1434 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
1435 size
: bpy
.props
.EnumProperty(
1438 ('1',"1x1",'','',0),
1439 ('2',"2x2",'','',1),
1440 ('3',"4x4",'','',2),
1441 ('4',"8x8",'','',3),
1442 ('5',"16x16",'','',4),
1443 ('6',"32x32",'','',5),
1444 ('7',"64x64",'','',6),
1445 ('8',"128x128",'','',7),
1446 ('9',"256x256",'','',8)
1448 description
="Texture resolution",
1451 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
1452 entity
: bpy
.props
.BoolProperty(name
="")
1454 enum_pointents
= [('NONE',"None","")]
1455 enum_brushents
= [('NONE',"None","")]
1457 for classname
in cxr_entities
:
1458 entdef
= cxr_entities
[classname
]
1459 if 'allow' in entdef
:
1460 itm
= [(classname
, classname
, "")]
1461 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
1462 else: enum_brushents
+= itm
1464 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
1465 update
=cxr_entity_changeclass
, default
='NONE' )
1467 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
1468 update
=cxr_entity_changeclass
, default
='NONE' )
1470 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
1471 last_hash
: bpy
.props
.StringProperty( name
="" )
1472 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
1474 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
1475 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
1476 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
1478 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
1479 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
1480 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
1481 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
1482 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
1483 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
1484 opt_vrad
: bpy
.props
.StringProperty( name
="args" )
1486 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
1487 scale_factor
: bpy
.props
.FloatProperty(name
="VMF Scale factor",default
=32.0,min=1.0)
1488 skybox_scale_factor
: bpy
.props
.FloatProperty(name
="Sky Scale factor",default
=1.0,min=0.01)
1489 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
1490 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
1491 displacement_cardinal
: bpy
.props
.BoolProperty(name
="Cardinal displacements",default
=True)
1492 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",default
=True)
1493 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",default
=12)
1495 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1496 bl_idname
="convexer.detect_compilers"
1497 bl_label
="Find compilers"
1499 def execute(self
,context
):
1500 scene
= context
.scene
1501 settings
= scene
.cxr_data
1502 subdir
= settings
.subdir
1504 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1505 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1506 if os
.path
.exists(searchpath
):
1507 settings
[F
'exe_{exename}'] = searchpath
1511 classes
= [ CXR_RELOAD
, CXR_DECOMPOSE_SOLID
, CXR_INTERFACE
, \
1512 CXR_WRITE_VMF
, CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
1513 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
1514 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
1515 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
]
1518 global debug_draw_handler
, vmt_param_dynamic_class
1521 bpy
.utils
.register_class(c
)
1523 # Build dynamic VMT properties class defined by cxr_shader_params
1524 annotations_dict
= {}
1526 def _dvmt_propogate(layer
):
1527 nonlocal annotations_dict
1530 if isinstance(layer
[decl
],dict): # $property definition
1534 if pdef
['type'] == 'bool':
1535 prop
= bpy
.props
.BoolProperty(\
1536 name
= pdef
['name'],\
1537 default
= pdef
['default'])
1539 elif pdef
['type'] == 'float':
1540 prop
= bpy
.props
.FloatProperty(\
1541 name
= pdef
['name'],\
1542 default
= pdef
['default'])
1544 elif pdef
['type'] == 'vector':
1545 if 'subtype' in pdef
:
1546 prop
= bpy
.props
.FloatVectorProperty(\
1547 name
= pdef
['name'],\
1548 subtype
= pdef
['subtype'],\
1549 default
= pdef
['default'],\
1550 size
= len(pdef
['default']))
1552 prop
= bpy
.props
.FloatVectorProperty(\
1553 name
= pdef
['name'],\
1554 default
= pdef
['default'],\
1555 size
= len(pdef
['default']))
1557 elif pdef
['type'] == 'string':
1558 prop
= bpy
.props
.StringProperty(\
1559 name
= pdef
['name'],\
1560 default
= pdef
['default'])
1562 elif pdef
['type'] == 'enum':
1563 prop
= bpy
.props
.EnumProperty(\
1564 name
= pdef
['name'],\
1565 items
= pdef
['items'],\
1566 default
= pdef
['default'])
1569 annotations_dict
[decl
] = prop
1571 # Recurse into sub-definitions
1572 _dvmt_propogate(pdef
)
1574 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
1577 cxr_shaders
[_
]["name"],\
1578 '') for _
in cxr_shaders
],\
1579 default
= next(iter(cxr_shaders
)))
1581 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
1583 _dvmt_propogate( cxr_shader_params
)
1584 vmt_param_dynamic_class
= type(
1586 (bpy
.types
.PropertyGroup
,),{
1587 "__annotations__": annotations_dict
1591 bpy
.utils
.register_class( vmt_param_dynamic_class
)
1594 bpy
.types
.Material
.cxr_data
= \
1595 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
1596 bpy
.types
.Image
.cxr_data
= \
1597 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
1598 bpy
.types
.Object
.cxr_data
= \
1599 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
1600 bpy
.types
.Collection
.cxr_data
= \
1601 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
1602 bpy
.types
.Light
.cxr_data
= \
1603 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
1604 bpy
.types
.LightProbe
.cxr_data
= \
1605 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
1606 bpy
.types
.Scene
.cxr_data
= \
1607 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
1609 # CXR Scene settings
1612 debug_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
1613 cxr_draw
,(),'WINDOW','POST_VIEW')
1615 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
1616 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
1619 global debug_draw_handler
, vmt_param_dynamic_class
1621 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
1623 bpy
.utils
.unregister_class(c
)
1625 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
1626 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
1628 bpy
.types
.SpaceView3D
.draw_handler_remove(debug_draw_handler
,'WINDOW')