219 lines
6.9 KiB
Python
219 lines
6.9 KiB
Python
bl_info = {
|
|
"name": "Snow Trail Export",
|
|
"author": "Snow Trail",
|
|
"version": (1, 0, 0),
|
|
"blender": (5, 0, 0),
|
|
"location": "3D Viewport > Sidebar > Snow Trail",
|
|
"description": "One-click glTF export to project assets folder",
|
|
"category": "Import-Export",
|
|
}
|
|
|
|
import bpy
|
|
from pathlib import Path
|
|
|
|
|
|
def find_project_root():
|
|
blend_path = Path(bpy.data.filepath)
|
|
if not blend_path.exists():
|
|
return None
|
|
candidate = blend_path.parent
|
|
while candidate != candidate.parent:
|
|
if (candidate / "assets").is_dir() and (candidate / "blender").is_dir():
|
|
return candidate
|
|
candidate = candidate.parent
|
|
return None
|
|
|
|
|
|
def get_export_path():
|
|
blend_path = Path(bpy.data.filepath)
|
|
project_root = find_project_root()
|
|
if project_root is None:
|
|
return None
|
|
stem = blend_path.stem
|
|
return project_root / "assets" / "meshes" / f"{stem}.gltf"
|
|
|
|
|
|
class SNOWTRAIL_OT_export_gltf(bpy.types.Operator):
|
|
bl_idname = "snow_trail.export_gltf"
|
|
bl_label = "Export to Project"
|
|
bl_description = "Export selected objects as glTF to assets/meshes/"
|
|
bl_options = {'REGISTER'}
|
|
|
|
def execute(self, context):
|
|
if not bpy.data.filepath:
|
|
self.report({'ERROR'}, "Save the .blend file first")
|
|
return {'CANCELLED'}
|
|
|
|
export_path = get_export_path()
|
|
if export_path is None:
|
|
self.report({'ERROR'}, "Could not find project root (looking for assets/ + blender/ dirs)")
|
|
return {'CANCELLED'}
|
|
|
|
export_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
selected = [obj for obj in context.selected_objects if obj.type == 'MESH']
|
|
if not selected:
|
|
self.report({'ERROR'}, "No mesh objects selected")
|
|
return {'CANCELLED'}
|
|
|
|
props = context.scene.snow_trail_export
|
|
|
|
bpy.ops.export_scene.gltf(
|
|
filepath=str(export_path),
|
|
export_format='GLTF_SEPARATE',
|
|
use_selection=True,
|
|
export_apply=props.apply_modifiers,
|
|
export_yup=True,
|
|
export_texcoords=True,
|
|
export_normals=True,
|
|
export_colors=props.export_vertex_colors,
|
|
export_materials='NONE' if not props.export_materials else 'EXPORT',
|
|
export_animations=props.export_animations,
|
|
)
|
|
|
|
rel_path = export_path.relative_to(find_project_root())
|
|
self.report({'INFO'}, f"Exported → {rel_path}")
|
|
return {'FINISHED'}
|
|
|
|
|
|
class SNOWTRAIL_OT_export_heightmap(bpy.types.Operator):
|
|
bl_idname = "snow_trail.export_heightmap"
|
|
bl_label = "Bake Heightmap"
|
|
bl_description = "Run heightmap bake script for selected terrain"
|
|
bl_options = {'REGISTER'}
|
|
|
|
def execute(self, context):
|
|
project_root = find_project_root()
|
|
if project_root is None:
|
|
self.report({'ERROR'}, "Could not find project root")
|
|
return {'CANCELLED'}
|
|
|
|
script_path = project_root / "blender" / "scripts" / "generate_heightmap.py"
|
|
if not script_path.exists():
|
|
self.report({'ERROR'}, f"Script not found: {script_path}")
|
|
return {'CANCELLED'}
|
|
|
|
import importlib.util
|
|
spec = importlib.util.spec_from_file_location("generate_heightmap", str(script_path))
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
|
|
terrain_obj = context.active_object
|
|
if terrain_obj is None or terrain_obj.type != 'MESH':
|
|
self.report({'ERROR'}, "Select a mesh object first")
|
|
return {'CANCELLED'}
|
|
|
|
output_path = project_root / "assets" / "textures" / "terrain_heightmap.exr"
|
|
mod.bake_heightmap(
|
|
terrain_obj=terrain_obj,
|
|
resolution=context.scene.snow_trail_export.heightmap_resolution,
|
|
output_path=str(output_path),
|
|
)
|
|
|
|
self.report({'INFO'}, f"Heightmap → assets/textures/terrain_heightmap.exr")
|
|
return {'FINISHED'}
|
|
|
|
|
|
class SNOWTRAIL_ExportProperties(bpy.types.PropertyGroup):
|
|
apply_modifiers: bpy.props.BoolProperty(
|
|
name="Apply Modifiers",
|
|
default=True,
|
|
description="Apply modifiers before export",
|
|
)
|
|
export_vertex_colors: bpy.props.BoolProperty(
|
|
name="Vertex Colors",
|
|
default=True,
|
|
description="Export vertex colors",
|
|
)
|
|
export_materials: bpy.props.BoolProperty(
|
|
name="Materials",
|
|
default=False,
|
|
description="Export materials (usually not needed for retro aesthetic)",
|
|
)
|
|
export_animations: bpy.props.BoolProperty(
|
|
name="Animations",
|
|
default=False,
|
|
description="Export animations",
|
|
)
|
|
heightmap_resolution: bpy.props.IntProperty(
|
|
name="Heightmap Resolution",
|
|
default=1024,
|
|
min=128,
|
|
max=4096,
|
|
description="Resolution for heightmap bake",
|
|
)
|
|
|
|
|
|
class SNOWTRAIL_PT_export_panel(bpy.types.Panel):
|
|
bl_label = "Snow Trail Export"
|
|
bl_idname = "SNOWTRAIL_PT_export_panel"
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
bl_category = "Snow Trail"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
props = context.scene.snow_trail_export
|
|
|
|
export_path = get_export_path()
|
|
if export_path:
|
|
project_root = find_project_root()
|
|
rel = export_path.relative_to(project_root)
|
|
layout.label(text=f"Target: {rel}", icon='FILE')
|
|
elif not bpy.data.filepath:
|
|
layout.label(text="Save .blend file first!", icon='ERROR')
|
|
else:
|
|
layout.label(text="Project root not found!", icon='ERROR')
|
|
|
|
layout.separator()
|
|
|
|
box = layout.box()
|
|
box.label(text="glTF Options:", icon='MESH_DATA')
|
|
box.prop(props, "apply_modifiers")
|
|
box.prop(props, "export_vertex_colors")
|
|
box.prop(props, "export_materials")
|
|
box.prop(props, "export_animations")
|
|
|
|
selected_meshes = [o for o in context.selected_objects if o.type == 'MESH']
|
|
row = layout.row()
|
|
row.scale_y = 1.5
|
|
row.operator("snow_trail.export_gltf", icon='EXPORT')
|
|
if not selected_meshes:
|
|
row.enabled = False
|
|
|
|
if selected_meshes:
|
|
layout.label(text=f"{len(selected_meshes)} mesh(es) selected")
|
|
else:
|
|
layout.label(text="Select mesh objects to export", icon='INFO')
|
|
|
|
layout.separator()
|
|
|
|
box = layout.box()
|
|
box.label(text="Terrain Tools:", icon='WORLD')
|
|
box.prop(props, "heightmap_resolution")
|
|
box.operator("snow_trail.export_heightmap", icon='IMAGE_DATA')
|
|
|
|
|
|
classes = (
|
|
SNOWTRAIL_ExportProperties,
|
|
SNOWTRAIL_OT_export_gltf,
|
|
SNOWTRAIL_OT_export_heightmap,
|
|
SNOWTRAIL_PT_export_panel,
|
|
)
|
|
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
bpy.types.Scene.snow_trail_export = bpy.props.PointerProperty(type=SNOWTRAIL_ExportProperties)
|
|
|
|
|
|
def unregister():
|
|
del bpy.types.Scene.snow_trail_export
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|