Last active
July 16, 2023 06:11
-
-
Save Qix-/41825b2d3321b9134e8837ff49861787 to your computer and use it in GitHub Desktop.
Revisions
-
Qix- revised this gist
Jul 16, 2023 . 1 changed file with 17 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -7,6 +7,7 @@ This is the "F**cking Import my Mesh!!!" (.FIM) format for interop of **simple** - Dead simple vertex / normal / vertex color **mesh** exporter from Blender to Unity. - File format is ASCII and dead simple to parse (see C# script). - Supports multiple objects in the same file - just have those meshes selected when exporting. - Automatic modifier apply and triangulate on export. ## Using @@ -48,6 +49,22 @@ Then switch "Domain" to "Vertex" and "Datatype" to "Color". Then re-export the FIM file. #### My model looks puffy and bloated. Yeah I'm still trying to figure out how Blender automatically determines hard/smooth edges. For example, this....  Turns into this:  The solution is to mark all of your edges as hard seams and use the `Edge Split` modifier with the `Angle` turned off, _OR_ to simply use the `Edge Split` modifier with an angle of your choice. If you want all edges to be sharp, set it to 0.  If you have any idea how Blender does 'magic' edge splitting, please let me know. #### Why can't I have multiple vertex color groups? Unfortunately, Unity doesn't provide an API to do this. It gives you a single group. -
Qix- revised this gist
Jul 16, 2023 . 2 changed files with 34 additions and 20 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -10,7 +10,7 @@ This is the "F**cking Import my Mesh!!!" (.FIM) format for interop of **simple** ## Using Install the Python script in Blender via the Addons menu. Then enable the "F**king Import my Mesh (.FIM) Export" addon. Have at least one mesh selected (non-meshes get ignored) and hit the export button. **There are no options for export.** FIM is pretty cut and dry. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,6 +6,18 @@ from bpy.props import StringProperty, BoolProperty, EnumProperty from bpy.types import Operator bl_info = { 'name': 'F**king Import my Mesh!!! (.FIM) Export', 'author': 'Josh Junon', 'version': (1, 0, 0), 'blender': (3, 4, 0), 'location': 'File > Export > F**king Import my Mesh (.FIM)', 'description': 'Export .FIM file for Unity import', 'tracker_url': 'https://gist.github.com/Qix-/41825b2d3321b9134e8837ff49861787', "support": "COMMUNITY", 'category': 'Import-Export' } AXIS_NAME = { 'Z': '+Z', 'POS_Z': '+Z', @@ -34,9 +46,10 @@ class ExportFIM(Operator, ExportHelper): maxlen=255, # Max internal buffer length, longer would be clamped. ) def execute(self, context): original_active = context.view_layer.objects.active meshes = list( map( lambda mesh: (mesh, self.apply_modifiers_and_extract_data(context, mesh)), @@ -46,32 +59,32 @@ def execute(self, context): ) ) ) if len(meshes) == 0: self.report({"ERROR"}, "No meshes are selected (FIM only supports meshes)") return {'FINISHED'} with open(self.filepath, 'w', encoding='utf-8') as fd: for mesh, data in meshes: fd.write("MESH\n") print(f"v{len(data['vertices'])} c{len(data['vertex_colors'])}") fd.write(f" META\n") fd.write(f" upaxis {AXIS_NAME[mesh.up_axis]}\n") fd.write(f" forwardaxis {AXIS_NAME[mesh.track_axis]}\n") fd.write(f" ATEM\n") fd.write(" VERT\n") for v in data['vertices']: fd.write(f" {v[0]},{v[1]},{v[2]}\n") fd.write(" TREV\n") fd.write(" NORM\n") for n in data['normals']: fd.write(f" {n[0]},{n[1]},{n[2]}\n") fd.write(" MRON\n") fd.write(" FACE") for i, t in enumerate(data['triangles']): if (i % 3) == 0: @@ -80,21 +93,21 @@ def execute(self, context): fd.write(" ") fd.write(f"{t}") fd.write("\n ECAF\n") if data['vertex_colors'] is not None: fd.write(" COLR\n") for color in data['vertex_colors']: fd.write(f" {color[0]},{color[1]},{color[2]},{color[3]}\n") fd.write(" RLOC\n") fd.write("HSEM\n") context.view_layer.objects.active = original_active return {'FINISHED'} def apply_modifiers_and_extract_data(self, context, obj): # Ensure the object is active and selected context.view_layer.objects.active = obj @@ -116,13 +129,13 @@ def apply_modifiers_and_extract_data(self, context, obj): # Apply Triangulate modifier to the copy triangulate_modifier = obj_copy.modifiers.new(name="Triangulate", type='TRIANGULATE') triangulate_modifier.quad_method = 'SHORTEST_DIAGONAL' triangulate_modifier.min_vertices = 4 bpy.ops.object.modifier_apply(modifier=triangulate_modifier.name) # Extract vertex data vertices = [v.co for v in obj_copy.data.vertices] normals = [v.normal for v in obj_copy.data.vertices] triangles = [index for poly in obj_copy.data.polygons for index in poly.vertices] # Extract vertex colors if available @@ -158,7 +171,8 @@ def register(): def unregister(): bpy.utils.unregister_class(ExportFIM) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) if __name__ == "__main__": register() bpy.ops.fim_export.data('INVOKE_DEFAULT') -
Qix- revised this gist
Jul 16, 2023 . 1 changed file with 5 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -62,4 +62,8 @@ Yes. It's released under CC0, Unlicense, or Public Domain. Pick one that suits y #### Who made FIM? Me, [Josh Junon](https://github.com/qix-). #### Why does this format do ________? Wouldn't it be better to do ________? Yep, probably. This got the job done though. -
Qix- revised this gist
Jul 16, 2023 . 1 changed file with 13 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -19,6 +19,19 @@ were present prior to creating the script in the project will need to be re-load ## FAQ #### Just... why? .OBJ exports from Blender include vertex color information but Unity doesn't parse it. .PLY isn't supported by Unity neither exporter from Blender properly triangulates meshes. .FBX is bloated. .GLTwhatever isn't supported by Unity. And I was tired of exporting materials and lights and cameras and whatever nonsense when _I just wanted a f**cking mesh_. I do a lot of procedural and minimalistic art work and don't need all the fancy Pabst Blue-Ribbon rendering. #### Why aren't my vertex colors being exported? You're probably using the default vertex color thing that stores them to face corners. -
Qix- created this gist
Jul 16, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,263 @@ using UnityEngine; using UnityEditor.AssetImporters; using System; using System.IO; using System.Text.RegularExpressions; using System.Collections.Generic; [ScriptedImporter(1, "fim")] public class FIMImporter : ScriptedImporter { public float m_Scale = 1; public override void OnImportAsset(AssetImportContext ctx) { var tokens = Regex.Split(File.ReadAllText(ctx.assetPath), @"\s+"); var i = 0; var depth = 0; var num_meshes = 0; for (; i < tokens.Length; i++) { var t = tokens[i]; switch (t) { case "": // Edge case where the end of the file has a newline/space/etc. break; case "MESH": ++i; ++depth; var mesh_id = num_meshes++; var mesh = new Mesh(); // Meta information (collected first, then applied after) var up_axis = Vector3.up; for (; i < tokens.Length; i++) { var t2 = tokens[i]; switch (t2) { case "HSEM": --depth; goto commit_mesh; case "META": ++i; ++depth; for (; i < tokens.Length; i++) { var t3 = tokens[i]; if (t3 == "ATEM") { --depth; break; } if (++i >= tokens.Length) { // Meh, we could probably handle this better but this is simpler. throw new Exception(string.Format("unexpected EOF")); } var k = t3; var v = tokens[i]; switch (k) { case "upaxis": switch (v) { case "+Z": up_axis = new Vector3(0, 0, 1); break; case "-Z": up_axis = new Vector3(0, 0, -1); break; case "+Y": up_axis = new Vector3(0, 1, 0); break; case "-Y": up_axis = new Vector3(0, -1, 0); break; case "+X": up_axis = new Vector3(1, 0, 0); break; case "-X": up_axis = new Vector3(-1, 0, 0); break; default: Debug.LogWarningFormat("FIM meta upaxis has unknown axis: {0} (mesh offset {1}; skipping upaxis)", v, mesh_id); break; } break; case "forwardaxis": // Unused property break; default: ++i; Debug.LogWarningFormat("FIM meta unknown property name: {0} (mesh offset {1}; skipping meta)", k, mesh_id); break; } } break; case "VERT": ++i; ++depth; var vertices = new List<Vector3>(); for (; i < tokens.Length; i++) { var t3 = tokens[i]; if (t3 == "TREV") { --depth; break; } string[] vals = t3.Split(','); if (vals.Length != 3) { Debug.LogWarningFormat("FIM mesh vertex is malformed: {0} (in mesh offset {1}; skipping vertex)", t3, mesh_id); continue; } var vert = new Vector3(float.Parse(vals[0]), float.Parse(vals[1]), float.Parse(vals[2])); vertices.Add(vert); } mesh.vertices = vertices.ToArray(); break; case "NORM": ++i; ++depth; var normals = new List<Vector3>(); for (; i < tokens.Length; i++) { var t3 = tokens[i]; if (t3 == "MRON") { --depth; break; } string[] vals = t3.Split(','); if (vals.Length != 3) { Debug.LogWarningFormat("FIM mesh normal is malformed: {0} (in mesh offset {1}; skipping normal)", t3, mesh_id); continue; } var norm = new Vector3(float.Parse(vals[0]), float.Parse(vals[1]), float.Parse(vals[2])); normals.Add(norm); } mesh.normals = normals.ToArray(); break; case "FACE": ++i; ++depth; var tri_list = new List<int>(); for (; i < tokens.Length; i++) { var t3 = tokens[i]; if (t3 == "ECAF") { --depth; break; } tri_list.Add(int.Parse(t3)); } mesh.triangles = tri_list.ToArray(); break; case "COLR": ++i; ++depth; var colors = new List<Color>(); for (; i < tokens.Length; i++) { var t3 = tokens[i]; if (t3 == "RLOC") { --depth; break; } string[] vals = t3.Split(','); if (vals.Length < 3 || vals.Length > 4) { Debug.LogWarningFormat("FIM mesh color is malformed: {0} (in mesh offset {1}; skipping color)", t3, mesh_id); continue; } var color = new Color(float.Parse(vals[0]), float.Parse(vals[1]), float.Parse(vals[2]), vals.Length == 4 ? float.Parse(vals[3]) : 1.0f); colors.Add(color); } mesh.colors = colors.ToArray(); break; default: Debug.LogWarningFormat("FIM unknown MESH block token: {0} (in mesh offset {1}; skipping block)", t2, mesh_id); { var end = ReverseString(t2); ++depth; for (; i < tokens.Length; i++) { if (tokens[i] == end) { --depth; break; } } } break; } } break; commit_mesh: ReorientMesh(mesh, up_axis); ctx.AddObjectToAsset(mesh_id.ToString(), mesh); break; default: Debug.LogWarningFormat("FIM unknown top-level token: {0} (skipping block)", t); { var end = ReverseString(t); ++depth; for (; i < tokens.Length; i++) { if (tokens[i] == end) { --depth; break; } } } break; } } if (depth > 0) { throw new System.Exception("FIM parser hit EOF unexpectedly"); } } static string ReverseString(string input) { char[] charArray = input.ToCharArray(); Array.Reverse(charArray); return new string(charArray); } static void ReorientMesh(Mesh mesh, Vector3 up_axis) { if (up_axis == Vector3.up) return; // Calculate the rotation quaternion to align the mesh with the custom axes Quaternion rotation = Quaternion.FromToRotation(up_axis, Vector3.up); // Get the vertices and normals from the mesh Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; // Apply the rotation to the vertices and normals for (int i = 0; i < vertices.Length; i++) { vertices[i] = rotation * vertices[i]; normals[i] = rotation * normals[i]; } // Update the modified vertices and normals back to the mesh mesh.vertices = vertices; mesh.normals = normals; // Recalculate the bounds and tangents of the mesh mesh.RecalculateBounds(); mesh.RecalculateTangents(); } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,52 @@ # .FIM Mesh Format This is the "F**cking Import my Mesh!!!" (.FIM) format for interop of **simple** models between Blender and Unity. ## Features - Dead simple vertex / normal / vertex color **mesh** exporter from Blender to Unity. - File format is ASCII and dead simple to parse (see C# script). - Supports multiple objects in the same file - just have those meshes selected when exporting. ## Using Run the attached Python script in Blender. I'm honestly not sure how to auto-load it but it will register and show up as an Export type. Have at least one mesh selected (non-meshes get ignored) and hit the export button. **There are no options for export.** FIM is pretty cut and dry. Then, have the attached Unity C# script present somewhere in your project's Assets folder. That's it. Any existing .FIM files that were present prior to creating the script in the project will need to be re-loaded. ## FAQ #### Why aren't my vertex colors being exported? You're probably using the default vertex color thing that stores them to face corners.  Select the group, then go to the Convert Color Attribute dialog:  Then switch "Domain" to "Vertex" and "Datatype" to "Color".  Then re-export the FIM file. #### Why can't I have multiple vertex color groups? Unfortunately, Unity doesn't provide an API to do this. It gives you a single group. #### Can I export UVs? Yeah it's certainly possible. I haven't needed them though. Feel free to comment here and I can probably help you out. #### Can I use the FIM format in other programs? Yes. It's released under CC0, Unlicense, or Public Domain. Pick one that suits you best. #### Who made FIM? Me, [Josh Junon](https://github.com/qix-). This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,164 @@ import bpy # ExportHelper is a helper class, defines filename and # invoke() function which calls the file selector. from bpy_extras.io_utils import ExportHelper from bpy.props import StringProperty, BoolProperty, EnumProperty from bpy.types import Operator AXIS_NAME = { 'Z': '+Z', 'POS_Z': '+Z', 'NEG_Z': '-Z', 'Y': '+Y', '-Y': 'Y', 'POS_Y': '+Y', 'NEG_Y': '-Y', 'X': '+X', '-X': 'X', 'POS_X': '+X', 'NEG_X': '-X' } class ExportFIM(Operator, ExportHelper): """Exports the selected mesh as an FIM (F**king Import my Mesh) format""" bl_idname = "fim_export.data" bl_label = "Export FIM" # ExportHelper mixin class uses this filename_ext = ".fim" filter_glob: StringProperty( default="*.fim", options={'HIDDEN'}, maxlen=255, # Max internal buffer length, longer would be clamped. ) def execute(self, context): original_active = context.view_layer.objects.active meshes = list( map( lambda mesh: (mesh, self.apply_modifiers_and_extract_data(context, mesh)), filter( lambda obj: obj.type == "MESH", context.selected_objects ) ) ) if len(meshes) == 0: self.report({"ERROR"}, "No meshes are selected (FIM only supports meshes)") return {'FINISHED'} with open(self.filepath, 'w', encoding='utf-8') as fd: for mesh, data in meshes: fd.write("MESH\n") print(f"v{len(data['vertices'])} c{len(data['vertex_colors'])}") fd.write(f" META\n") fd.write(f" upaxis {AXIS_NAME[mesh.up_axis]}\n") fd.write(f" forwardaxis {AXIS_NAME[mesh.track_axis]}\n") fd.write(f" ATEM\n") fd.write(" VERT\n") for v in data['vertices']: fd.write(f" {v[0]},{v[1]},{v[2]}\n") fd.write(" TREV\n") fd.write(" NORM\n") for n in data['normals']: fd.write(f" {n[0]},{n[1]},{n[2]}\n") fd.write(" MRON\n") fd.write(" FACE") for i, t in enumerate(data['triangles']): if (i % 3) == 0: fd.write("\n ") else: fd.write(" ") fd.write(f"{t}") fd.write("\n ECAF\n") if data['vertex_colors'] is not None: fd.write(" COLR\n") for color in data['vertex_colors']: fd.write(f" {color[0]},{color[1]},{color[2]},{color[3]}\n") fd.write(" RLOC\n") fd.write("HSEM\n") context.view_layer.objects.active = original_active return {'FINISHED'} def apply_modifiers_and_extract_data(self, context, obj): # Ensure the object is active and selected context.view_layer.objects.active = obj obj.select_set(True) # Create a copy of the object obj_copy = obj.copy() obj_copy.data = obj.data.copy() context.collection.objects.link(obj_copy) # Ensure the copy object is active and selected context.view_layer.objects.active = obj_copy obj_copy.select_set(True) # Apply all modifiers to the copy modifiers = obj_copy.modifiers for modifier in modifiers: bpy.ops.object.modifier_apply(modifier=modifier.name) # Apply Triangulate modifier to the copy triangulate_modifier = obj_copy.modifiers.new(name="Triangulate", type='TRIANGULATE') triangulate_modifier.quad_method = 'BEAUTY' triangulate_modifier.min_vertices = 4 bpy.ops.object.modifier_apply(modifier=triangulate_modifier.name) # Extract vertex data vertices = [v.co for v in obj_copy.data.vertices] normals = [[n for n in obj_copy.data.vertices[i].normal] for i in range(len(obj_copy.data.vertices))] triangles = [index for poly in obj_copy.data.polygons for index in poly.vertices] # Extract vertex colors if available vertex_colors = None if obj_copy.data.color_attributes and len(obj_copy.data.color_attributes) >= 1: if len(obj_copy.data.color_attributes) > 1: self.report({'WARNING'}, f"Mesh '{obj.name}' has multiple vertex color attribute groups; choosing the first") vertex_colors = [v.color for v in obj_copy.data.color_attributes[0].data] # After exporting, remove the temporary copy from the scene bpy.data.objects.remove(obj_copy, do_unlink=True) # Return the extracted data as properties in a dictionary return { 'vertices': vertices, 'normals': normals, 'triangles': triangles, 'vertex_colors': vertex_colors, } # Only needed if you want to add into a dynamic menu def menu_func_export(self, context): self.layout.operator(ExportFIM.bl_idname, text="F**king Import my Mesh (.FIM)") # Register and add to the "file selector" menu (required to use F3 search "Text Export Operator" for quick access). def register(): bpy.utils.register_class(ExportFIM) bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): bpy.utils.unregister_class(ExportFIM) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) if __name__ == "__main__": register() bpy.ops.fim_export.data('INVOKE_DEFAULT')