# ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "Export Half-Life 1 Tools Format(.map)", "author": "Ludovic Pouzenc", "version": (0, 1, 0), "blender": (2, 79, 0), "location": "File > Export > Half-Life 1 tools (.map)", "description": "Export mesh to Half-Life 1 tools (.map)", "warning": "", #"wiki_url": "http://to.do", "category": "Import-Export"} """ Related links: https://developer.valvesoftware.com/wiki/MAP_file_format Usage Notes: Map file could be opened in Valve Hammer 3.4 editor (prefered bit windows only) or passed to orignal's or ZLHT's compiler tools (use wine to run them on linux) MAP format defines solids (brushes) as a set of intersecting planes. Planes a defined by 3 points in space (could be different from face's vertices) This is (very) unusual. This cannot reprents things such as : * Concave meshes * Non-planar faces Existing tools that use this .map format probably don't cope with : * ngons with n > 32 For now this script do : * For each mesh in current scene ** Simplify it (remove doubles in vertices, fill holes, connect vertices to not have concave faces nor non planar faces) ** Recalc face normals to have them pointing to this outside of the mesh * For each object in current scene that is type 'MESH' ** Take the mesh and local to world matrix ** Make a .map brush for each mesh's blender face (world coords, scaled by blender_to_map_scale_factor) ** Put AAATRIGGER texture stupidly ** Output it as part of worldspawn entity in .map """ if "bpy" in locals(): import importlib # if "import_3ds" in locals(): # importlib.reload(import_hl1map) if "export_hl1map" in locals(): importlib.reload(export_hl1map) import bpy from bpy.props import ( BoolProperty, CollectionProperty, FloatProperty, StringProperty, ) from bpy_extras.io_utils import ( # ImportHelper, ExportHelper, ) #class ImportHL1MAP(bpy.types.Operator, ImportHelper): # """Import Half-Life 1 Tools Format(.map)""" # bl_idname = "import_scene.hl1_map" # bl_label = 'Half-Life 1 tools (.map)' # bl_options = {'UNDO'} # filename_ext = ".map" # filter_glob = StringProperty(default="*.map", options={'HIDDEN'}) # constrain_size = FloatProperty( # name="Size Constraint", # description="Scale the model by 10 until it reaches the " # "size constraint (0 to disable)", # min=0.0, max=1000.0, # soft_min=0.0, soft_max=1000.0, # default=10.0, # ) # use_image_search = BoolProperty( # name="Image Search", # description="Search subdirectories for any associated images " # "(Warning, may be slow)", # default=True, # ) # use_apply_transform = BoolProperty( # name="Apply Transform", # description="Workaround for object transformations " # "importing incorrectly", # default=True, # ) # def execute(self, context): # from . import import_3ds # keywords = self.as_keywords(ignore=("axis_forward", # "axis_up", # "filter_glob", # )) # global_matrix = axis_conversion(from_forward=self.axis_forward, # from_up=self.axis_up, # ).to_4x4() # keywords["global_matrix"] = global_matrix # return import_3ds.load(self, context, **keywords) class WorldSpawnEntityProp(bpy.types.PropertyGroup): value = bpy.props.StringProperty(name='') class ExportHL1MAP(bpy.types.Operator, ExportHelper): """Export Half-Life 1 Tools Format(.map)""" bl_idname = 'export_scene.hl1_map' bl_label = 'Export HL1 .map' filename_ext = '.map' filter_glob = StringProperty(default='*'+filename_ext, options={'HIDDEN'}) use_selection = BoolProperty( name='Selection Only', description='Export selected objects only', default=False, ) blender_to_map_scale_factor = FloatProperty( name='Upscaling factor', description='Scale from blender world coordinates to map brush coordinates', default=100, ) # self.attrs is a list of objects representing application specific and dynamically set attributes attrs = [] # https://developer.valvesoftware.com/wiki/MAP_file_format collection = CollectionProperty( name='Worldspawn entity properties', description='All non-entity brushes in .map belongs to a Worldspawn entity that have mandatory properties to be ran in-game', type=WorldSpawnEntityProp, ) worldspawn_props = { 'classname': 'worldspawn', 'sounds': 1, 'MaxRange': 4096, 'mapversion': 220, 'wad': '\\half-life\\valve\\xeno.wad;\\half-life\\valve\\decals.wad;\\half-life\\valve\\halflife.wad;\\half-life\\valve\\liquids.wad' } def invoke(self, context, event): self.collection.clear() for attr in self.attrs: # create a new item in self.collection collectionItem = self.collection.add() collectionItem.name = attr.name collectionItem.value = attr.value attr.collectionItem = collectionItem return super(ExportHL1MAP, self).invoke(context, event) # FIXME https://blenderartists.org/t/how-to-set-a-property-for-an-operator-dynamically/615897/6 # def draw(self, context): # layout = self.layout # for attr in self.attrs: # attrName = attr[0] # row = self.layout.split() # # it's not possible to set the name for an item in self.collection, # # however it's possible to set a label dynamically # row.label(attr.name) # # the following property refers to the attribute <value> of the item in self.collection # # remember the attribute was defined in the class CustomFloatProperty # row.prop(attr.collectionItem, "value") # return super(ExportHL1MAP, self).draw(context) def execute(self, context): from . import export_hl1map keywords = self.as_keywords(ignore=('filter_glob','check_existing')) keywords['worldspawn_props'] = self.worldspawn_props print('export_hl1map.save(self, context, ', keywords, ')') return export_hl1map.save(self, context, **keywords) # Add to a menu def menu_func_export(self, context): self.layout.operator(ExportHL1MAP.bl_idname, text="Half-Life 1 tools (.map)") #def menu_func_import(self, context): # self.layout.operator(ImportHL1MAP.bl_idname, text="Half-Life 1 tools (.map)") def register(): bpy.utils.register_module(__name__) # bpy.types.INFO_MT_file_import.append(menu_func_import) bpy.types.INFO_MT_file_export.append(menu_func_export) def unregister(): bpy.utils.unregister_module(__name__) # bpy.types.INFO_MT_file_import.remove(menu_func_import) bpy.types.INFO_MT_file_export.remove(menu_func_export) if __name__ == "__main__": register()