Last active
May 20, 2024 17:07
-
-
Save Oppodelldog/9b1d53f411420e32bae485536418148d to your computer and use it in GitHub Desktop.
blender add on that supports export to godot and creating mesh colliders in blender
This file contains 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 characters
bl_info = { | |
"name": "Godot Colliders and Export Addon", | |
"blender": (3, 0, 0), | |
"category": "Object", | |
"description": "Creates and removes collider objects and exports GLTF files easily", | |
} | |
import bpy | |
import os | |
bpy.types.Scene.gltf_export_target_path = bpy.props.StringProperty() | |
addon_keymaps = [] | |
# Collider Object Addon Classes | |
class OBJECT_OT_create_collider(bpy.types.Operator): | |
bl_idname = "object.create_collider" | |
bl_label = "Create Collider" | |
bl_options = {'REGISTER', 'UNDO'} | |
def execute(self, context): | |
selected_objects = bpy.context.selected_objects | |
if not selected_objects: | |
self.report({'WARNING'}, "No objects selected") | |
return {'CANCELLED'} | |
for original in selected_objects: | |
new_object_name = "_collider-" + original.name + "-colonly" | |
# Remove existing collider with the same name | |
for child in original.children: | |
if child.name == new_object_name: | |
bpy.data.objects.remove(child, do_unlink=True) | |
# Duplicate the object and set it as a child with the new name | |
bpy.ops.object.select_all(action='DESELECT') | |
original.select_set(True) | |
bpy.context.view_layer.objects.active = original | |
bpy.ops.object.duplicate(linked=True) | |
new_obj = bpy.context.selected_objects[0] | |
# Convert geometry nodes to mesh | |
bpy.context.view_layer.objects.active = new_obj | |
bpy.ops.object.convert(target='MESH') | |
# Set the duplicate's transformation properties to match the original object | |
new_obj.location = original.location | |
new_obj.rotation_euler = original.rotation_euler | |
new_obj.scale = original.scale | |
new_obj.parent = original | |
new_obj.name = new_object_name | |
new_obj.hide_viewport = True | |
# Remove all geometry node modifiers from the new object | |
for modifier in new_obj.modifiers: | |
if modifier.type == 'NODES': | |
new_obj.modifiers.remove(modifier) | |
# restore original selection | |
bpy.ops.object.select_all(action='DESELECT') | |
for obj in selected_objects: | |
obj.select_set(True) | |
return {'FINISHED'} | |
class OBJECT_OT_remove_colliders(bpy.types.Operator): | |
bl_idname = "object.remove_colliders" | |
bl_label = "Remove Colliders" | |
bl_options = {'REGISTER', 'UNDO'} | |
def execute(self, context): | |
selected_objects = bpy.context.selected_objects | |
if not selected_objects: | |
self.report({'WARNING'}, "No objects selected") | |
return {'CANCELLED'} | |
colliders_to_remove = [] | |
def collect_colliders(obj): | |
if obj.name.startswith("_collider-") and obj.name.endswith("-colonly"): | |
colliders_to_remove.append(obj) | |
for child in obj.children: | |
collect_colliders(child) | |
# Collect colliders from all selected objects | |
for original in selected_objects: | |
collect_colliders(original) | |
# Remove collected colliders | |
for collider in colliders_to_remove: | |
bpy.data.objects.remove(collider, do_unlink=True) | |
return {'FINISHED'} | |
class SetGLFTExportTargetPath(bpy.types.Operator): | |
bl_idname = "wm.godot_gltf_export_set_export_target_path" | |
bl_label = "Set Export Target Path" | |
gltf_export_target_path: bpy.props.StringProperty(name="Path") | |
def execute(self, context): | |
scene = context.scene | |
scene.gltf_export_target_path = self.gltf_export_target_path | |
return {'FINISHED'} | |
def invoke(self, context, event): | |
wm = context.window_manager | |
self.gltf_export_target_path = context.scene.gltf_export_target_path | |
return wm.invoke_props_dialog(self) | |
def draw(self, context): | |
layout = self.layout | |
layout.prop(self, "gltf_export_target_path") | |
class GodotShowColliders(bpy.types.Operator): | |
bl_idname = "wm.godot_show_colliders" | |
bl_label = "Show Colliders" | |
def execute(self, context): | |
selected_objects = bpy.context.selected_objects | |
if not selected_objects: | |
self.report({'WARNING'}, "No objects selected") | |
return {'CANCELLED'} | |
for obj in selected_objects: | |
showColliders(obj) | |
return {'FINISHED'} | |
class GodotHideColliders(bpy.types.Operator): | |
bl_idname = "wm.godot_hide_colliders" | |
bl_label = "Hide Colliders" | |
def execute(self, context): | |
selected_objects = bpy.context.selected_objects | |
if not selected_objects: | |
self.report({'WARNING'}, "No objects selected") | |
return {'CANCELLED'} | |
for obj in selected_objects: | |
hideColliders(obj) | |
return {'FINISHED'} | |
class GodotSelectColliders(bpy.types.Operator): | |
bl_idname = "wm.godot_select_colliders" | |
bl_label = "Select Colliders" | |
def execute(self, context): | |
selected_objects = bpy.context.selected_objects | |
if not selected_objects: | |
self.report({'WARNING'}, "No objects selected") | |
return {'CANCELLED'} | |
bpy.ops.object.select_all(action='DESELECT') | |
for obj in selected_objects: | |
selectColliders(obj) | |
return {'FINISHED'} | |
class GodotGLTFExportSelected(bpy.types.Operator): | |
bl_idname = "wm.godot_gltf_export_selected" | |
bl_label = "Export Selected" | |
def execute(self, context): | |
print("Godot Export: Starting") | |
scene = context.scene | |
if not scene.gltf_export_target_path: | |
self.report({'ERROR'}, "gltf export path not set") | |
return {'FINISHED'} | |
bpy.ops.wm.godot_show_colliders() | |
original_selected = allSelected(bpy.context.selected_objects) | |
selectList(original_selected, False); | |
export_count = 0 | |
for obj in original_selected: | |
collectionName = get_collection_name(obj) | |
path = "{0}\\{1}\\{2}".format(scene.gltf_export_target_path, collectionName, obj.name) | |
if not os.path.exists(path): | |
os.makedirs(path) | |
filename = r'{0}\\{1}.gltf'.format(path, obj.name) | |
selectBranch(obj, True) | |
bpy.ops.export_scene.gltf(filepath=filename, | |
export_format="GLB", | |
use_selection=True, | |
use_visible=False, | |
export_apply=True, | |
export_gn_mesh=True | |
) | |
selectBranch(obj, False) | |
export_count += 1 | |
print("Godot Export: exported to {0}".format(filename)) | |
selectList(original_selected, True) | |
bpy.ops.wm.godot_hide_colliders() | |
self.report({'INFO'}, "Godot Export finished ({0})".format(export_count)) | |
print("Godot Export: Finished") | |
return {'FINISHED'} | |
# Utility Functions | |
def is_collider(obj): | |
return obj.name.startswith("_collider-") and obj.name.endswith("-colonly") | |
def get_collection_name(obj): | |
for collection in bpy.data.collections: | |
if obj.name in collection.objects: | |
return collection.name | |
return None | |
def showColliders(obj): | |
if is_collider(obj): | |
obj.hide_viewport = False | |
for child in obj.children: | |
showColliders(child) | |
def hideColliders(obj): | |
if is_collider(obj): | |
obj.hide_viewport = True | |
for child in obj.children: | |
hideColliders(child) | |
def selectColliders(obj): | |
if is_collider(obj): | |
obj.hide_viewport = False | |
obj.select_set(True) | |
for child in obj.children: | |
selectColliders(child) | |
def selectList(objs, select): | |
for child in objs: | |
child.select_set(select) | |
def selectBranch(obj, select): | |
obj.select_set(select) | |
for child in obj.children: | |
child.select_set(select) | |
selectBranch(child, select) | |
def allSelected(objects): | |
all_selected = [] | |
for obj in objects: | |
if obj.select_get(): | |
all_selected.append(obj) | |
obj.select_set(False) | |
all_selected.extend(allSelected(obj.children)) | |
return all_selected | |
# Panel Class | |
class OBJECT_PT_collider_and_gltf_panel(bpy.types.Panel): | |
bl_label = "Godot Export" | |
bl_idname = "OBJECT_PT_collider_and_gltf_panel" | |
bl_space_type = 'VIEW_3D' | |
bl_region_type = 'UI' | |
bl_category = 'Godot' | |
def draw(self, context): | |
layout = self.layout | |
layout.operator("object.create_collider") | |
layout.operator("object.remove_colliders") | |
layout.operator("wm.godot_select_colliders") | |
layout.operator("wm.godot_show_colliders") | |
layout.operator("wm.godot_hide_colliders") | |
layout.separator() | |
layout.operator("wm.godot_gltf_export_set_export_target_path") | |
layout.operator("wm.godot_gltf_export_selected") | |
# Register and Unregister Classes | |
classes = [ | |
OBJECT_OT_create_collider, | |
OBJECT_OT_remove_colliders, | |
SetGLFTExportTargetPath, | |
GodotSelectColliders, | |
GodotShowColliders, | |
GodotHideColliders, | |
GodotGLTFExportSelected, | |
OBJECT_PT_collider_and_gltf_panel | |
] | |
def menu_func(self, context): | |
self.layout.operator(GodotGLTFExportSelected.bl_idname, text="Godot Export Selected") | |
self.layout.operator(SetGLFTExportTargetPath.bl_idname, text="Godot Export Set Target Path") | |
self.layout.operator(GodotShowColliders.bl_idname, text="Godot Show Colliders") | |
self.layout.operator(GodotHideColliders.bl_idname, text="Godot Hide Colliders") | |
def register(): | |
for cls in classes: | |
bpy.utils.register_class(cls) | |
bpy.types.VIEW3D_MT_view.append(menu_func) | |
wm = bpy.context.window_manager | |
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY") | |
kmi = km.keymap_items.new(GodotGLTFExportSelected.bl_idname, 'D', 'PRESS', ctrl=True, shift=True) | |
addon_keymaps.append((km, kmi)) | |
km = wm.keyconfigs.addon.keymaps.new(name="Mesh", space_type="EMPTY") | |
kmi = km.keymap_items.new(GodotGLTFExportSelected.bl_idname, 'D', 'PRESS', ctrl=True, shift=True) | |
addon_keymaps.append((km, kmi)) | |
km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER") | |
kmi = km.keymap_items.new(GodotGLTFExportSelected.bl_idname, 'D', 'PRESS', ctrl=True, shift=True) | |
addon_keymaps.append((km, kmi)) | |
km = wm.keyconfigs.addon.keymaps.new(name="Sculpt Mode", space_type="EMPTY") | |
kmi = km.keymap_items.new(GodotGLTFExportSelected.bl_idname, 'D', 'PRESS', ctrl=True, shift=True) | |
addon_keymaps.append((km, kmi)) | |
def unregister(): | |
for cls in reversed(classes): | |
bpy.utils.unregister_class(cls) | |
wm = bpy.context.window_manager | |
for km, kmi in addon_keymaps: | |
km.keymap_items.remove(kmi) | |
addon_keymaps.clear() | |
if __name__ == "__main__": | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment