feat: Mass export to several files

This commit is contained in:
yannk 2023-03-29 12:43:53 +02:00
parent 314a219186
commit bd95d68ae4
14 changed files with 298 additions and 103 deletions

View file

@ -16,24 +16,24 @@
# #
# END GPL LICENSE BLOCK ##### # END GPL LICENSE BLOCK #####
bl_info = { bl_info = {
"name": "Khanat Tools", "name": "Khanat tools",
"author": "Yann Kervran", "author": "Yann Kervran",
"version": (1, 0, 0), "version": (1, 0, 0),
"blender": (3, 4, 0), "blender": (3, 4, 0),
"location": "View3D > UI > Tools", "location": "View3D > UI > N Panel",
"description": "Toolset for Khanat project", "description": "Toolset for Khanat project",
"doc_url": "https://git.numenaute.org/yannk/khanat-tools", "doc_url": "https://git.numenaute.org/yannk/khanat-tools",
"category": "Generic" "tracker_url": "https://git.numenaute.org/yannk/khanat-tools/-/issues",
"category": "Generic"
} }
def register(): def register():
from addon.register import register_addon from .addon.register import register_addon
register_addon() register_addon()
def unregister(): def unregister():
from addon.register import unregister_addon from .addon.register import unregister_addon
unregister_addon() unregister_addon()

0
addon/common/__init__.py Normal file
View file

11
addon/common/addon.py Normal file
View file

@ -0,0 +1,11 @@
import bpy
"""
Parameters & functions to get information from addon
"""
addon_name = __name__.partition('.')[0]
def get_prefs():
return bpy.context.preferences.addons[addon_name].preferences

16
addon/common/icons.py Normal file
View file

@ -0,0 +1,16 @@
import bpy
import os
icons_collection = None
icons_directory = os.path.dirname(__file__)
def get_icon_id(identifier):
return get_icon(identifier).icon_id
def get_icon(identifier):
if identifier in icons_collection:
return icons_collection[identifier]
return icons_collection.load(identifier, os.path.join(icons_directory, identifier + ".png"), "IMAGE")

View file

@ -0,0 +1,11 @@
import re
def validate_name(name):
"""Check if the name is properly formatted
Must be in kebab-case, with possible underscore for prefixes/sufixes
and only alphanumerical
A-z0-9 doesnt seem to work in regexp, so had to write them full extent
"""
allowed = re.match(r'^([ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]+)_?([ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-]+)_?([ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]+)$', name)
return allowed is not None

View file

@ -1,29 +1,21 @@
import bpy import bpy
import os import os
icons_collection = None from ..common import icons
icons_directory = os.path.dirname(__file__)
def initialize_icons_collection(): def initialize_icons_collection():
import bpy.utils.previews import bpy.utils.previews
global icons_collection icons.icons_collection = bpy.utils.previews.new()
print("icons_collection : {}".format(icons_collection))
icons_collection = bpy.utils.previews.new()
def unload_icons(): def unload_icons():
bpy.utils.previews.remove(icons_collection) bpy.utils.previews.remove(icons.icons_collection)
def get_icon_id(identifier):
# The initialize_icons_collection function needs to be called first.
return get_icon(identifier).icon_id
def get_icon(identifier):
if identifier in icons_collection:
return icons_collection[identifier]
return icons_collection.load(identifier, os.path.join(icons_directory, identifier + ".png"), "IMAGE")
def register_icons(): def register_icons():
initialize_icons_collection() initialize_icons_collection()
def unregister_icons(): def unregister_icons():
unload_icons() unload_icons()

View file

@ -1,18 +1,15 @@
import bpy
from .panel_main import KH_PT_panel_main from .panel_main import KH_PT_panel_main
classes = ( classes = (
KH_PT_panel_main, KH_PT_panel_main,
) )
def register_menus(): def register_menus():
from bpy.utils import register_class from bpy.utils import register_class
for cls in classes: for cls in classes:
register_class(cls) register_class(cls)
def unregister_menus(): def unregister_menus():
from bpy.utils import unregister_class from bpy.utils import unregister_class
for cls in classes: for cls in classes:
unregister_class(cls) unregister_class(cls)

View file

@ -1,26 +1,31 @@
import bpy import bpy
from ..operators import readthedocs, export2godot from ..operators import readthedocs, export2godot
from ..icons import get_icon_id from ..common import icons
class KH_PT_panel_main(bpy.types.Panel): class KH_PT_panel_main(bpy.types.Panel):
bl_label = 'Khanat tools' """
bl_space_type = 'VIEW_3D' Main panel in 3D View
bl_region_type = 'UI' """
bl_context = ''
bl_category = 'Khanat'
bl_label = 'Khanat tools'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_context = ''
bl_category = 'Khanat'
def draw_header(self, context): def draw_header(self, context):
layout = self.layout layout = self.layout
layout.label(icon_value=get_icon_id("khanat")) layout.label(icon_value=icons.get_icon_id("khanat"))
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
row = layout.row() row = layout.row()
row.operator(export2godot.KH_OP_export2godot.bl_idname) row.operator(export2godot.KH_OT_export2godot.bl_idname)
layout.separator() layout.separator()
row = layout.row() row = layout.row()
row.operator(readthedocs.KH_OP_readthedocs.bl_idname, icon_value=72) row.operator(readthedocs.KH_OT_readthedocs.bl_idname, icon_value=72)
layout.separator() layout.separator()

View file

@ -1,20 +1,18 @@
import bpy from .readthedocs import KH_OT_readthedocs
from .export2godot import KH_OT_export2godot
from .readthedocs import KH_OP_readthedocs
from .export2godot import KH_OP_export2godot
classes = ( classes = (
KH_OP_readthedocs, KH_OT_readthedocs,
KH_OP_export2godot KH_OT_export2godot
) )
def register_operators(): def register_operators():
print(classes) from bpy.utils import register_class
from bpy.utils import register_class for cls in classes:
for cls in classes: register_class(cls)
register_class(cls)
def unregister_operators(): def unregister_operators():
from bpy.utils import unregister_class from bpy.utils import unregister_class
for cls in classes: for cls in classes:
unregister_class(cls) unregister_class(cls)

View file

@ -1,14 +1,113 @@
import bpy import bpy
import os
import re
class KH_OP_export2godot(bpy.types.Operator): from ..common import addon
"""Export collections to Godot throught glTF""" from ..common import validate_name
class KH_OT_export2godot(bpy.types.Operator):
"""Export whole collections to Godot throught glTF format"""
bl_idname = "kh.export2godot" bl_idname = "kh.export2godot"
bl_label = "Export to gltf" bl_label = "Export to Godot project"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
def invoke(self, context, event): def invoke(self, context, event):
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
print("TOTOR IS NOT HERE") prefs = addon.get_prefs()
return {"FINISHED"} print("Root collection : {}".format(prefs.root_collection))
print("Godot path : {}".format(prefs.godot_project_path))
print("Blender repository path : {}".format(
prefs.blender_repository_path))
def save_file_beforehand():
self.report(
{"WARNING"}, "File must be saved first - skipping export")
bpy.ops.wm.save_mainfile('INVOKE_AREA')
return {"CANCELLED"}
def create_destination_folder():
"""
Define the destination folder and creates it if nonexistent.
Return the path name
"""
final_path = bpy.data.filepath.replace(
prefs.blender_repository_path, prefs.godot_project_path)
gltf_path = os.path.splitext(final_path)[0]
if not os.path.isdir(gltf_path):
os.makedirs(gltf_path)
return gltf_path
def recurLayerCollection(layerColl, collName):
""" Activate the selected collection for export"""
found = None
if (layerColl.name == collName):
return layerColl
for layer in layerColl.children:
found = recurLayerCollection(layer, collName)
if found:
return found
def export_content(scene_collection):
def check_name(collection):
is_valid = validate_name.validate_name(collection.name)
if not is_valid:
self.report({"WARNING"},
"Name {} is not valid - skipping export".format(collection.name))
return {"CANCELLED"}
def export_to_file(tscn_collection):
print("Exporting {}".format(tscn_collection.name))
filename = tscn_collection.name
# Export collection - parameters : https://docs.blender.org/api/current/bpy.ops.export_scene.html?highlight=export_scene#bpy.ops.export_scene.gltf
print("Exporting to : {}/{}".format(gltf_path, filename))
print("Exporting textures to : {}_{}_textures".format((os.path.splitext(os.path.basename(gltf_path)))[0], filename))
bpy.ops.export_scene.gltf(
filepath="{}/{}".format(gltf_path, filename),
export_format="GLTF_SEPARATE", # Export glTF Separate (.gltf + .bin + textures), Exports multiple files, with separate JSON, binary and texture data
export_texture_dir="{}_{}_textures".format((os.path.splitext(os.path.basename(gltf_path)))[0], filename), # Textures folder
export_copyright=prefs.licence,
use_active_collection = True,
use_renderable = True,
export_cameras=False,
export_lights=False,
export_apply=True # Applique les modifiers
)
tscn_collections = [ coll for coll in bpy.data.collections if scene_collection.user_of_id(coll)]
for tscn in tscn_collections:
print("--------------------")
check_name(tscn)
# Activate proper collection for export
layer_collection = bpy.context.view_layer.layer_collection
layerColl = recurLayerCollection(layer_collection, tscn.name)
if layerColl.exclude:
print("{} is not activated - not exported".format(tscn.name))
else:
print("Set active collection to {}".format(tscn.name))
bpy.context.view_layer.active_layer_collection = layerColl
export_to_file(tscn)
if not bpy.data.is_saved:
save_file_beforehand()
else:
try:
scn_col = bpy.data.collections[prefs.root_collection]
except KeyError:
self.report({"WARNING"},
"No \"{}\" root collection in the file - skipping export".format(prefs.root_collection))
return {"CANCELLED"}
# Create the proper destination path
gltf_path = create_destination_folder()
export_content(scn_col)
return {"FINISHED"}

View file

@ -1,20 +1,17 @@
import bpy import bpy
class KH_OT_readthedocs(bpy.types.Operator):
"""Check online documentation for development"""
class KH_OP_readthedocs(bpy.types.Operator): bl_idname="kh.readthedocs"
"""Check online documentation""" bl_label="Online documentation"
bl_description="Go to Khanat Development Guide"
bl_options= {"REGISTER", "UNDO"}
bl_idname = "kh.readthedocs" def invoke (self, context, event):
bl_label = "Online documentation" return self.execute(context)
bl_description = "Go to Khanat Development Guide"
bl_options = {"REGISTER", "UNDO"}
def invoke(self, context, event):
return self.execute(context)
def execute(self, context):
bpy.ops.wm.url_open('INVOKE_DEFAULT', url='https://git.numenaute.org/khaganat/mmorpg_khanat/khanat_gamedev_guide')
return {"FINISHED"}
def execute(self, context):
bpy.ops.wm.url_open("INVOKE_DEFAULT", url="https://git.numenaute.org/khaganat/mmorpg_khanat/khanat_gamedev_guide")
return {"FINISHED"}

View file

@ -0,0 +1,18 @@
import bpy
from .preferences import KH_Prefs
classes = (
KH_Prefs,
)
def register_preferences():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister_preferences():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)

View file

@ -0,0 +1,50 @@
import bpy
from bpy.props import StringProperty, IntProperty, BoolProperty
from ..common import addon
class KH_Prefs(bpy.types.AddonPreferences):
bl_idname = addon.addon_name
# TODO EnumProperty to get list of projects to choose from
godot_project_path: StringProperty(
name="Godot project path",
subtype='DIR_PATH',
default="//godot_project/"
)
blender_repository_path: StringProperty(
name="Blender repository root",
subtype='DIR_PATH',
default="//"
)
root_collection: StringProperty(
name="Root collection",
default="khanat",
)
licence: StringProperty(
name="Licence",
default="CC BY SA Khaganat",
)
contributor: StringProperty(
name="Contributor",
default="",
)
def draw(self, context):
layout = self.layout
box = layout.box()
box.prop(self, "godot_project_path")
box.prop(self, "blender_repository_path")
box.prop(self, "root_collection")
layout.split()
box = layout.box()
box.prop(self, "licence")
box.prop(self, "contributor")

View file

@ -1,30 +1,31 @@
def register_addon(): def register_addon():
# Icons from ..preferences import register_preferences
from ..icons import register_icons register_preferences()
register_icons()
# Operators
from ..operators import register_operators from ..operators import register_operators
register_operators() register_operators()
# Menus from ..icons import register_icons
register_icons()
from ..menus import register_menus from ..menus import register_menus
register_menus() register_menus()
def unregister_addon(): def unregister_addon():
# Menus
from ..menus import unregister_menus from ..menus import unregister_menus
unregister_menus() unregister_menus()
# Operators from ..icons import unregister_icons
unregister_icons()
from ..operators import unregister_operators from ..operators import unregister_operators
unregister_operators() unregister_operators()
# Icons from ..preferences import unregister_preferences
from ..icons import unregister_icons unregister_preferences()
unregister_icons()