├── screenshots ├── .gdignore ├── exporter_option.png ├── godot_screenshot.png └── blender_screenshot.png ├── icon.png ├── addons └── CSGExporter │ ├── csg_exporter_icon.png │ ├── plugin.cfg │ ├── csg_exporter_icon.png.import │ ├── CSGExporterMenuButton.tscn │ ├── CSGExporter.gd │ └── CSGExporterMenuButton.gd ├── .gitignore ├── default_env.tres ├── project.godot ├── icon.png.import ├── LICENSE ├── README.md └── demo.tscn /screenshots/.gdignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henriquelalves/GodotCSGExporter/HEAD/icon.png -------------------------------------------------------------------------------- /screenshots/exporter_option.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henriquelalves/GodotCSGExporter/HEAD/screenshots/exporter_option.png -------------------------------------------------------------------------------- /screenshots/godot_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henriquelalves/GodotCSGExporter/HEAD/screenshots/godot_screenshot.png -------------------------------------------------------------------------------- /screenshots/blender_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henriquelalves/GodotCSGExporter/HEAD/screenshots/blender_screenshot.png -------------------------------------------------------------------------------- /addons/CSGExporter/csg_exporter_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henriquelalves/GodotCSGExporter/HEAD/addons/CSGExporter/csg_exporter_icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Godot-specific ignores 3 | .import/ 4 | export.cfg 5 | export_presets.cfg 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | data_*/ 10 | -------------------------------------------------------------------------------- /addons/CSGExporter/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="CSGExporter" 4 | description="A tool to export CSG Meshes." 5 | author="Henrique Alves" 6 | version="1.0" 7 | script="CSGExporter.gd" 8 | -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | _global_script_classes=[ ] 12 | _global_script_class_icons={ 13 | 14 | } 15 | 16 | [application] 17 | 18 | config/name="CSGExporter" 19 | config/description="An addon to export CSG Meshes as OBJ files." 20 | run/main_scene="res://demo.tscn" 21 | config/icon="res://icon.png" 22 | 23 | [editor_plugins] 24 | 25 | enabled=PoolStringArray( "CSGExporter" ) 26 | 27 | [rendering] 28 | 29 | environment/default_environment="res://default_env.tres" 30 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/CSGExporter/csg_exporter_icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/csg_exporter_icon.png-d21c171ff267b6c3143fc456d9c89e30.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/CSGExporter/csg_exporter_icon.png" 13 | dest_files=[ "res://.import/csg_exporter_icon.png-d21c171ff267b6c3143fc456d9c89e30.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/CSGExporter/CSGExporterMenuButton.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://addons/CSGExporter/CSGExporterMenuButton.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/CSGExporter/csg_exporter_icon.png" type="Texture" id=2] 5 | 6 | [node name="CSGExporterMenuButton" type="MenuButton"] 7 | anchor_right = 1.0 8 | anchor_bottom = 1.0 9 | text = "Export CSG" 10 | icon = ExtResource( 2 ) 11 | items = [ "Export to OBJ", null, 0, false, false, 0, 0, null, "", false ] 12 | script = ExtResource( 1 ) 13 | __meta__ = { 14 | "_edit_use_anchors_": false 15 | } 16 | 17 | [node name="FileDialog" type="FileDialog" parent="."] 18 | anchor_left = 0.5 19 | anchor_top = 0.5 20 | anchor_right = 0.5 21 | anchor_bottom = 0.5 22 | margin_left = -357.5 23 | margin_top = -226.5 24 | margin_right = 357.5 25 | margin_bottom = 226.5 26 | access = 2 27 | show_hidden_files = true 28 | current_dir = "/home/henrique/Dev/godot/projects/CSGExporter" 29 | current_path = "/home/henrique/Dev/godot/projects/CSGExporter/" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Henrique Lacreta Alves 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/CSGExporter/CSGExporter.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | var exporter_button_scene = preload("CSGExporterMenuButton.tscn") 5 | var exporter_button = null 6 | 7 | func _enter_tree(): 8 | exporter_button = exporter_button_scene.instance() 9 | add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, exporter_button) 10 | exporter_button.hide() 11 | exporter_button.connect("finished_exporting", self, "_on_finished_exporting") 12 | 13 | func handles(object): 14 | var is_exportable_csg = object is CSGShape and object.is_root_shape() 15 | 16 | if is_exportable_csg: 17 | exporter_button.show() 18 | exporter_button.focused_csg_mesh = object 19 | else: 20 | exporter_button.hide() 21 | 22 | return is_exportable_csg 23 | 24 | func _on_finished_exporting(): 25 | get_editor_interface().get_resource_filesystem().scan() 26 | 27 | func clear(): 28 | exporter_button.hide() 29 | 30 | func make_visible(visible): 31 | exporter_button.visible = visible 32 | 33 | func _exit_tree(): 34 | if (exporter_button): 35 | remove_control_from_container(EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU, exporter_button) 36 | exporter_button.queue_free() 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot CSG Exporter 2 | 3 | *For more addons, check [my GitHub](https://github.com/henriquelalves) and [my itch.io](https://perons.itch.io/) page.* 4 | 5 | Godot editor plugin to export CSG Meshes as Wavefront (.obj) files. 6 | 7 | ![Godot CSG Mesh](screenshots/godot_screenshot.png) 8 | ![Exporter Wavefront Mesh on Blender](screenshots/blender_screenshot.png) 9 | 10 | For now the plugin is only able to export the raw mesh, not any materials appended to it. 11 | 12 | **Disclaimer** To fix the CSGMesh material exporting, I merged mohammedzero43's CSG exporter with some slightly tweaks; the original code can be found here: https://github.com/mohammedzero43/CSGExport-Godot. Thanks for letting me use it! 13 | 14 | ## How to install 15 | Add the addon on your Godot project (you may copy the addon from this project folder, or install it via AssetLib). Then activate it on the project settings "Plugins" tab. 16 | 17 | ## How to use it 18 | Whenever you click on a root CSGMesh, the addon will add a "Export CSG" button to the Spatial Editor upper bar. This will open a File Dialog popup so you can choose where to save the exported file. 19 | ![Exporter option on Godot spatial container](screenshots/exporter_option.png) 20 | 21 | ## Why using it 22 | CSG Meshes are quite useful to prototyping and making quick modifications on levels, but sometimes you may want to export the mesh made in Godot so it can be edited in other software (e.g. Blender) 23 | -------------------------------------------------------------------------------- /demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [sub_resource type="SpatialMaterial" id=1] 4 | albedo_color = Color( 0.0509804, 0.945098, 0.964706, 1 ) 5 | 6 | [sub_resource type="SpatialMaterial" id=2] 7 | albedo_color = Color( 0.976471, 0.0431373, 0.0431373, 1 ) 8 | 9 | [sub_resource type="SpatialMaterial" id=3] 10 | albedo_color = Color( 0, 1, 0.0156863, 1 ) 11 | 12 | [sub_resource type="SpatialMaterial" id=4] 13 | albedo_color = Color( 0.243137, 0.380392, 0.992157, 1 ) 14 | 15 | [node name="Demo" type="Spatial"] 16 | 17 | [node name="CSGBox" type="CSGBox" parent="."] 18 | width = 13.0123 19 | depth = 12.8443 20 | 21 | [node name="CSGCylinder" type="CSGCylinder" parent="CSGBox"] 22 | transform = Transform( 0.999998, -0.00145829, 0.00139671, 0.00146491, 0.0478624, -0.998853, 0.00138976, 0.998853, 0.0478644, -0.203619, 0.698373, 0.0496392 ) 23 | operation = 2 24 | height = 10.534 25 | material = SubResource( 1 ) 26 | 27 | [node name="CSGBox" type="CSGBox" parent="CSGBox"] 28 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -4.55753, 1, 0 ) 29 | depth = 8.0 30 | material = SubResource( 2 ) 31 | 32 | [node name="CSGTorus2" type="CSGTorus" parent="CSGBox/CSGBox"] 33 | transform = Transform( 0.0367483, -0.999325, 0, 0.999325, 0.0367483, 0, 0, 0, 1, 0.577628, 1.20979, 0.0002141 ) 34 | operation = 1 35 | inner_radius = 1.21926 36 | outer_radius = 2.5888 37 | material = SubResource( 3 ) 38 | 39 | [node name="CSGTorus" type="CSGTorus" parent="CSGBox"] 40 | transform = Transform( 0.0367483, -0.999325, 0, 0.999325, 0.0367483, 0, 0, 0, 1, 3.91512, 0.907835, 0 ) 41 | material = SubResource( 4 ) 42 | 43 | [node name="Camera" type="Camera" parent="."] 44 | transform = Transform( 0.679014, 0.262326, -0.685656, 0, 0.933978, 0.357331, 0.734125, -0.242633, 0.634184, -13.7388, 8.19551, 13.2599 ) 45 | 46 | [node name="DirectionalLight" type="DirectionalLight" parent="."] 47 | transform = Transform( 0.183378, -0.366753, 0.912066, -0.0734647, 0.920092, 0.384751, -0.980294, -0.137559, 0.141781, 0, 7.38757, 11.2751 ) 48 | -------------------------------------------------------------------------------- /addons/CSGExporter/CSGExporterMenuButton.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends MenuButton 3 | 4 | enum EXPORT_TYPE {OBJ} 5 | 6 | signal finished_exporting 7 | 8 | var focused_csg_mesh : CSGShape 9 | var file_dialog : FileDialog 10 | var export_type 11 | 12 | func _ready(): 13 | get_popup().connect("index_pressed", self, "_on_popup_index_pressed") 14 | 15 | file_dialog = $FileDialog 16 | file_dialog.mode = FileDialog.MODE_SAVE_FILE 17 | file_dialog.access = FileDialog.ACCESS_FILESYSTEM 18 | file_dialog.connect("file_selected", self, "_on_file_selected") 19 | 20 | func _on_popup_index_pressed(id : int): 21 | if (id == 0): 22 | export_type = EXPORT_TYPE.OBJ 23 | file_dialog.show() 24 | 25 | func _on_file_selected(path : String): 26 | if (export_type == EXPORT_TYPE.OBJ): 27 | _save_mesh_to_obj(path) 28 | 29 | func _save_mesh_to_obj(path : String): 30 | # Variables 31 | var objcont = "" #.obj content 32 | var matcont = "" #.mat content 33 | var csgMesh= focused_csg_mesh.get_meshes(); 34 | var vertcount=0 35 | 36 | # OBJ Headers 37 | objcont+="mtllib "+path.get_file() + ".mtl\n" 38 | objcont+="o ./" + path.get_file() + "\n"; 39 | 40 | # Blank material 41 | var blank_material = SpatialMaterial.new() 42 | blank_material.resource_name = "BlankMaterial" 43 | 44 | #Get surfaces and mesh info 45 | for t in range(csgMesh[-1].get_surface_count()): 46 | var surface = csgMesh[-1].surface_get_arrays(t) 47 | var verts = surface[0] 48 | var UVs = surface[4] 49 | var normals = surface[1] 50 | var mat : SpatialMaterial = csgMesh[-1].surface_get_material(t) 51 | if (mat == null): 52 | mat = blank_material 53 | var faces = [] 54 | 55 | #create_faces_from_verts (Triangles) 56 | var tempv=0 57 | for v in range(verts.size()): 58 | if tempv%3==0: 59 | faces.append([]) 60 | faces[-1].append(v+1) 61 | tempv+=1 62 | tempv= tempv%3 63 | 64 | #add verticies 65 | var tempvcount =0 66 | for ver in verts: 67 | objcont+=str("v ",ver[0],' ',ver[1],' ',ver[2])+"\n" 68 | tempvcount +=1 69 | 70 | #add UVs 71 | for uv in UVs: 72 | objcont+=str("vt ",uv[0],' ',uv[1])+"\n" 73 | for norm in normals: 74 | objcont+=str("vn ",norm[0],' ',norm[1],' ',norm[2])+"\n" 75 | 76 | #add groups and materials 77 | objcont+="g surface"+str(t)+"\n" 78 | 79 | objcont+="usemtl "+str(mat)+"\n" 80 | 81 | #add faces 82 | for face in faces: 83 | objcont+=str("f ", face[2]+vertcount,"/",face[2]+vertcount,"/",face[2]+vertcount, 84 | ' ',face[1]+vertcount,"/",face[1]+vertcount,"/",face[1]+vertcount, 85 | ' ',face[0]+vertcount,"/",face[0]+vertcount,"/",face[0]+vertcount)+"\n" 86 | #update verts 87 | vertcount+=tempvcount 88 | 89 | matcont+=str("newmtl "+str(mat))+'\n' 90 | matcont+=str("Kd ",mat.albedo_color.r," ",mat.albedo_color.g," ",mat.albedo_color.b)+'\n' 91 | matcont+=str("Ke ",mat.emission.r," ",mat.emission.g," ",mat.emission.b)+'\n' 92 | matcont+=str("d ",mat.albedo_color.a)+"\n" 93 | 94 | #Write to files 95 | var objfile = File.new() 96 | objfile.open(path + ".obj", File.WRITE) 97 | objfile.store_string(objcont) 98 | objfile.close() 99 | 100 | var mtlfile = File.new() 101 | mtlfile.open(path + ".mtl", File.WRITE) 102 | mtlfile.store_string(matcont) 103 | mtlfile.close() 104 | 105 | #output message 106 | print("CSG Mesh Exported") 107 | emit_signal("finished_exporting") 108 | --------------------------------------------------------------------------------