Create vertex_animation.py
This commit is contained in:
parent
42196519ae
commit
6190f5aac5
|
@ -0,0 +1,255 @@
|
|||
# See notes for source repo
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
|
||||
bl_info = {
|
||||
"name": "Vertex Animation",
|
||||
"author": "Joshua Bogart",
|
||||
"version": (1, 0),
|
||||
"blender": (2, 83, 0),
|
||||
"location": "View3D > Sidebar > Vertex Animation",
|
||||
"description": "A tool for storing per frame vertex data for use in a vertex shader.",
|
||||
"warning": "",
|
||||
"doc_url": "",
|
||||
"category": "tool",
|
||||
}
|
||||
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import os
|
||||
|
||||
|
||||
def get_per_frame_mesh_data(context, data, objects):
|
||||
"""Return a list of combined mesh data per frame"""
|
||||
meshes = []
|
||||
for i in frame_range(context.scene):
|
||||
context.scene.frame_set(i)
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
bm = bmesh.new()
|
||||
for ob in objects:
|
||||
eval_object = ob.evaluated_get(depsgraph)
|
||||
me = data.meshes.new_from_object(eval_object)
|
||||
me.transform(ob.matrix_world)
|
||||
bm.from_mesh(me)
|
||||
data.meshes.remove(me)
|
||||
me = data.meshes.new("mesh")
|
||||
bm.to_mesh(me)
|
||||
bm.free()
|
||||
me.calc_normals()
|
||||
meshes.append(me)
|
||||
return meshes
|
||||
|
||||
|
||||
def create_export_mesh_object(context, data, me):
|
||||
"""Return a mesh object with correct UVs"""
|
||||
while len(me.uv_layers) < 2:
|
||||
me.uv_layers.new()
|
||||
uv_layer = me.uv_layers[1]
|
||||
uv_layer.name = "vertex_anim"
|
||||
for loop in me.loops:
|
||||
uv_layer.data[loop.index].uv = ((loop.vertex_index + 0.5)/len(me.vertices), 0.0)
|
||||
ob = data.objects.new("export_mesh", me)
|
||||
context.scene.collection.objects.link(ob)
|
||||
return ob
|
||||
|
||||
|
||||
def get_vertex_data(data, meshes):
|
||||
"""Return lists of vertex offsets and normals from a list of mesh data"""
|
||||
original = meshes[0].vertices
|
||||
offsets = []
|
||||
normals = []
|
||||
for me in meshes: #for me in reversed(meshes):
|
||||
for v in me.vertices:
|
||||
offset = v.co - original[v.index].co
|
||||
x, y, z = offset
|
||||
offsets.extend((x, -y, z, 1))
|
||||
x, y, z = v.normal
|
||||
normals.extend(((x + 1) * 0.5, (-y + 1) * 0.5, (z + 1) * 0.5, 1))
|
||||
#if not me.users:
|
||||
#data.meshes.remove(me)
|
||||
for me in meshes:
|
||||
if not me.users:
|
||||
data.meshes.remove(me)
|
||||
return offsets, normals
|
||||
|
||||
|
||||
def frame_range(scene):
|
||||
"""Return a range object with with scene's frame start, end, and step"""
|
||||
return range(scene.frame_start, scene.frame_end, scene.frame_step)
|
||||
|
||||
|
||||
def bake_vertex_data(context, data, offsets, normals, size):
|
||||
"""Stores vertex offsets and normals in separate image textures"""
|
||||
width, height = size
|
||||
|
||||
blend_path = bpy.data.filepath
|
||||
blend_path = os.path.dirname(bpy.path.abspath(blend_path))
|
||||
subfolder_path = os.path.join(blend_path, "vaexport")
|
||||
if not os.path.exists(subfolder_path):
|
||||
os.makedirs(subfolder_path)
|
||||
openexr_filepath = os.path.join(subfolder_path, "offsets.exr")
|
||||
png_filepath = os.path.join(subfolder_path, "normals.png")
|
||||
|
||||
openexr_export_scene = bpy.data.scenes.new('openexr export scene')
|
||||
openexr_export_scene.sequencer_colorspace_settings.name = 'Non-Color'
|
||||
openexr_export_scene.render.image_settings.color_depth = '16'
|
||||
openexr_export_scene.render.image_settings.color_mode = 'RGBA'
|
||||
openexr_export_scene.render.image_settings.file_format = 'OPEN_EXR'
|
||||
openexr_export_scene.render.image_settings.exr_codec = 'NONE'
|
||||
|
||||
if 'offsets' in bpy.data.images:
|
||||
offset_tex = bpy.data.images['offsets']
|
||||
bpy.data.images.remove(offset_tex)
|
||||
|
||||
offset_texture = data.images.new(
|
||||
name="offsets",
|
||||
width=width,
|
||||
height=height,
|
||||
alpha=True,
|
||||
float_buffer=True
|
||||
)
|
||||
|
||||
offset_texture.file_format = 'OPEN_EXR'
|
||||
offset_texture.colorspace_settings.name = 'Non-Color'
|
||||
offset_texture.pixels = offsets
|
||||
offset_texture.save_render(openexr_filepath, scene=openexr_export_scene)
|
||||
bpy.data.scenes.remove(openexr_export_scene)
|
||||
|
||||
png_export_scene = bpy.data.scenes.new('png export scene')
|
||||
png_export_scene.render.image_settings.color_depth = '8'
|
||||
png_export_scene.render.image_settings.color_mode = 'RGBA'
|
||||
png_export_scene.render.image_settings.file_format = 'PNG'
|
||||
png_export_scene.render.image_settings.compression = 15
|
||||
|
||||
if 'normals' in bpy.data.images:
|
||||
normals_tex = bpy.data.images['normals']
|
||||
bpy.data.images.remove(normals_tex)
|
||||
|
||||
normal_texture = data.images.new(
|
||||
name="normals",
|
||||
width=width,
|
||||
height=height,
|
||||
alpha=True
|
||||
)
|
||||
|
||||
normal_texture.file_format = 'PNG'
|
||||
normal_texture.pixels = normals
|
||||
normal_texture.save_render(png_filepath, scene=png_export_scene)
|
||||
bpy.data.scenes.remove(png_export_scene)
|
||||
|
||||
|
||||
class OBJECT_OT_ProcessAnimMeshes(bpy.types.Operator):
|
||||
"""Store combined per frame vertex offsets and normals for all
|
||||
selected mesh objects into seperate image textures"""
|
||||
bl_idname = "object.process_anim_meshes"
|
||||
bl_label = "Process Anim Meshes"
|
||||
|
||||
@property
|
||||
def allowed_modifiers(self):
|
||||
return [
|
||||
'ARMATURE', 'CAST', 'CURVE', 'DISPLACE', 'HOOK',
|
||||
'LAPLACIANDEFORM', 'LATTICE', 'MESH_DEFORM',
|
||||
'SHRINKWRAP', 'SIMPLE_DEFORM', 'SMOOTH',
|
||||
'CORRECTIVE_SMOOTH', 'LAPLACIANSMOOTH',
|
||||
'SURFACE_DEFORM', 'WARP', 'WAVE', 'MESH_CACHE'
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ob = context.active_object
|
||||
return ob and ob.type == 'MESH' and ob.mode == 'OBJECT'
|
||||
|
||||
def execute(self, context):
|
||||
units = context.scene.unit_settings
|
||||
data = bpy.data
|
||||
objects = [ob for ob in context.selected_objects if ob.type == 'MESH']
|
||||
vertex_count = sum([len(ob.data.vertices) for ob in objects])
|
||||
frame_count = len(frame_range(context.scene))
|
||||
for ob in objects:
|
||||
for mod in ob.modifiers:
|
||||
if mod.type not in self.allowed_modifiers:
|
||||
self.report(
|
||||
{'ERROR'},
|
||||
f"Objects with {mod.type.title()} modifiers are not allowed!"
|
||||
)
|
||||
return {'CANCELLED'}
|
||||
#if units.system != 'METRIC' or round(units.scale_length, 2) != 0.01:
|
||||
# self.report(
|
||||
# {'ERROR'},
|
||||
# "Scene Unit must be Metric with a Unit Scale of 0.01!"
|
||||
# )
|
||||
# return {'CANCELLED'}
|
||||
if vertex_count > 8192:
|
||||
self.report(
|
||||
{'ERROR'},
|
||||
f"Vertex count of {vertex_count :,}, execedes limit of 8,192!"
|
||||
)
|
||||
return {'CANCELLED'}
|
||||
if frame_count > 8192:
|
||||
self.report(
|
||||
{'ERROR'},
|
||||
f"Frame count of {frame_count :,}, execedes limit of 8,192!"
|
||||
)
|
||||
return {'CANCELLED'}
|
||||
meshes = get_per_frame_mesh_data(context, data, objects)
|
||||
export_mesh_data = meshes[0].copy()
|
||||
create_export_mesh_object(context, data, export_mesh_data)
|
||||
offsets, normals = get_vertex_data(data, meshes)
|
||||
texture_size = vertex_count, frame_count
|
||||
bake_vertex_data(context, data, offsets, normals, texture_size)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class VIEW3D_PT_VertexAnimation(bpy.types.Panel):
|
||||
"""Creates a Panel in 3D Viewport"""
|
||||
bl_label = "Vertex Animation"
|
||||
bl_idname = "VIEW3D_PT_vertex_animation"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Vertex Animation"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
scene = context.scene
|
||||
col = layout.column(align=True)
|
||||
col.prop(scene, "frame_start", text="Frame Start")
|
||||
col.prop(scene, "frame_end", text="End")
|
||||
col.prop(scene, "frame_step", text="Step")
|
||||
row = layout.row()
|
||||
row.operator("object.process_anim_meshes")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_ProcessAnimMeshes)
|
||||
bpy.utils.register_class(VIEW3D_PT_VertexAnimation)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_ProcessAnimMeshes)
|
||||
bpy.utils.unregister_class(VIEW3D_PT_VertexAnimation)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
Loading…
Reference in New Issue