Skip to content

Instantly share code, notes, and snippets.

@Martin-Pitt
Created January 7, 2026 19:23
Show Gist options
  • Select an option

  • Save Martin-Pitt/863987b3d46f3d8ffb8fe2d6d70f8044 to your computer and use it in GitHub Desktop.

Select an option

Save Martin-Pitt/863987b3d46f3d8ffb8fe2d6d70f8044 to your computer and use it in GitHub Desktop.
Blender Script to export a 31x31 subdivided grid plane to a SL megaprim sculpty
import bpy
import numpy as np
bl_info = {
"name": "Second Life Mega Sculpty Exporter",
"blender": (4, 5, 0),
"category": "Import-Export",
"version": (1, 0, 0),
"description": "Export 31x31 grid as a Second Life 1024x1024x256 megaprim sculpty PNG"
}
class EXPORT_OT_sculpty(bpy.types.Operator):
bl_idname = "export_scene.sculpty"
bl_label = "Export Sculpty"
bl_options = {'REGISTER', 'UNDO'}
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
obj = context.active_object
if not obj or obj.type != 'MESH':
self.report({'ERROR'}, "Select a mesh object")
return {'CANCELLED'}
mesh = obj.data
if len(mesh.vertices) != 1024:
self.report({'ERROR'}, "Mesh must have exactly 1024 vertices")
return {'CANCELLED'}
# Create Blender image (64x64)
img = bpy.data.images.new("sculpty", width=64, height=64)
# Prepare pixel data (RGBA format, 4 values per pixel)
pixels = [0.0] * (64 * 64 * 4)
# Map vertices to Second Life megaprim bounding box (1024x1024x256)
for idx, vertex in enumerate(mesh.vertices):
py = idx // 32
px = idx % 32
# Normalize to 0-1 range within megaprim bounds
x_norm = (vertex.co.x + 512) / 1024 # X: -512 to +512
y_norm = (vertex.co.y + 512) / 1024 # Y: -512 to +512
z_norm = (vertex.co.z) / 256 # Z: 0 to +256
# Clamp to 0-1 range
x_norm = max(0, min(1, x_norm))
y_norm = max(0, min(1, y_norm))
z_norm = max(0, min(1, z_norm))
# Map to 2x2 pixels in 64x64 image
for dy in range(2):
for dx in range(2):
pixel_y = py * 2 + dy
pixel_x = px * 2 + dx
pixel_idx = (pixel_y * 64 + pixel_x) * 4
pixels[pixel_idx] = x_norm # R (X position)
pixels[pixel_idx + 1] = y_norm # G (Y position)
pixels[pixel_idx + 2] = z_norm # B (Z position)
pixels[pixel_idx + 3] = 1.0 # A (opacity)
img.pixels[:] = pixels
img.save_render(self.filepath)
# Clean up
bpy.data.images.remove(img)
self.report({'INFO'}, f"Sculpty saved to {self.filepath}")
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def menu_export(self, context):
self.layout.operator(EXPORT_OT_sculpty.bl_idname, text="Second Life Mega Sculpty (.png)")
def register():
bpy.utils.register_class(EXPORT_OT_sculpty)
bpy.types.TOPBAR_MT_file_export.append(menu_export)
def unregister():
bpy.utils.unregister_class(EXPORT_OT_sculpty)
bpy.types.TOPBAR_MT_file_export.remove(menu_export)
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment