Skip to content

Commit

Permalink
add : auto export
Browse files Browse the repository at this point in the history
  • Loading branch information
yumataesu committed Apr 7, 2021
1 parent 6fd069a commit 509e069
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 39 deletions.
36 changes: 36 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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 3 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
# MERCHANTIBILITY 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, see <http://www.gnu.org/licenses/>.

bl_info = {
"name": "VAT Exporter for TouchDesigner",
"author": "YumaTaesu",
"version": (1, 1),
"blender": (2, 93, 0),
"location": "",
"description": "Vertex Animation Texture Exporter for TouchDesigner",
"warning": "",
"support": "TESTING",
"wiki_url": "",
"tracker_url": "",
"category": "Object"
}

from . import auto_load

auto_load.init()

def register():
auto_load.register()

def unregister():
auto_load.unregister()
157 changes: 157 additions & 0 deletions auto_load.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import os
import bpy
import sys
import typing
import inspect
import pkgutil
import importlib
from pathlib import Path

__all__ = (
"init",
"register",
"unregister",
)

blender_version = bpy.app.version

modules = None
ordered_classes = None

def init():
global modules
global ordered_classes

modules = get_all_submodules(Path(__file__).parent)
ordered_classes = get_ordered_classes_to_register(modules)

def register():
for cls in ordered_classes:
bpy.utils.register_class(cls)

for module in modules:
if module.__name__ == __name__:
continue
if hasattr(module, "register"):
module.register()

def unregister():
for cls in reversed(ordered_classes):
bpy.utils.unregister_class(cls)

for module in modules:
if module.__name__ == __name__:
continue
if hasattr(module, "unregister"):
module.unregister()


# Import modules
#################################################

def get_all_submodules(directory):
return list(iter_submodules(directory, directory.name))

def iter_submodules(path, package_name):
for name in sorted(iter_submodule_names(path)):
yield importlib.import_module("." + name, package_name)

def iter_submodule_names(path, root=""):
for _, module_name, is_package in pkgutil.iter_modules([str(path)]):
if is_package:
sub_path = path / module_name
sub_root = root + module_name + "."
yield from iter_submodule_names(sub_path, sub_root)
else:
yield root + module_name


# Find classes to register
#################################################

def get_ordered_classes_to_register(modules):
return toposort(get_register_deps_dict(modules))

def get_register_deps_dict(modules):
my_classes = set(iter_my_classes(modules))
my_classes_by_idname = {cls.bl_idname : cls for cls in my_classes if hasattr(cls, "bl_idname")}

deps_dict = {}
for cls in my_classes:
deps_dict[cls] = set(iter_my_register_deps(cls, my_classes, my_classes_by_idname))
return deps_dict

def iter_my_register_deps(cls, my_classes, my_classes_by_idname):
yield from iter_my_deps_from_annotations(cls, my_classes)
yield from iter_my_deps_from_parent_id(cls, my_classes_by_idname)

def iter_my_deps_from_annotations(cls, my_classes):
for value in typing.get_type_hints(cls, {}, {}).values():
dependency = get_dependency_from_annotation(value)
if dependency is not None:
if dependency in my_classes:
yield dependency

def get_dependency_from_annotation(value):
if blender_version >= (2, 93):
if isinstance(value, bpy.props._PropertyDeferred):
return value.keywords.get("type")
else:
if isinstance(value, tuple) and len(value) == 2:
if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty):
return value[1]["type"]
return None

def iter_my_deps_from_parent_id(cls, my_classes_by_idname):
if bpy.types.Panel in cls.__bases__:
parent_idname = getattr(cls, "bl_parent_id", None)
if parent_idname is not None:
parent_cls = my_classes_by_idname.get(parent_idname)
if parent_cls is not None:
yield parent_cls

def iter_my_classes(modules):
base_types = get_register_base_types()
for cls in get_classes_in_modules(modules):
if any(base in base_types for base in cls.__bases__):
if not getattr(cls, "is_registered", False):
yield cls

def get_classes_in_modules(modules):
classes = set()
for module in modules:
for cls in iter_classes_in_module(module):
classes.add(cls)
return classes

def iter_classes_in_module(module):
for value in module.__dict__.values():
if inspect.isclass(value):
yield value

def get_register_base_types():
return set(getattr(bpy.types, name) for name in [
"Panel", "Operator", "PropertyGroup",
"AddonPreferences", "Header", "Menu",
"Node", "NodeSocket", "NodeTree",
"UIList", "RenderEngine",
"Gizmo", "GizmoGroup",
])


# Find order to register to solve dependencies
#################################################

def toposort(deps_dict):
sorted_list = []
sorted_values = set()
while len(deps_dict) > 0:
unsorted = []
for value, deps in deps_dict.items():
if len(deps) == 0:
sorted_list.append(value)
sorted_values.add(value)
else:
unsorted.append(value)
deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted}
return sorted_list
Binary file removed example/VAT.blend
Binary file not shown.
Binary file added example/assets/model.fbx
Binary file not shown.
Binary file added example/assets/normals.exr
Binary file not shown.
Binary file added example/assets/offsets.exr
Binary file not shown.
Binary file removed example/normal.png
Binary file not shown.
Binary file removed example/offsets.exr
Binary file not shown.
Binary file modified example/test.toe
Binary file not shown.
120 changes: 81 additions & 39 deletions script/vat_for_td.py → operators.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@

import bpy
from bpy.props import *
import bmesh

bl_info = {
"name": "VAT for TouchDesigner",
"author": "YumaTaesu",
"version": (1, 0),
"blender": (2, 83, 0),
"location": "",
"description": "Export VAT for TouchDesigner",
"warning": "",
"support": "TESTING",
"wiki_url": "",
"tracker_url": "",
"category": "Object"
}


def create_mesh_sequence(context, data, object):
"""Return a list of combined mesh data per frame"""
meshes = []
for i in range(context.scene.frame_start, context.scene.frame_end):
s = context.scene.start_frame
e = context.scene.end_frame

for i in range(s, e+1):
context.scene.frame_set(i)
depsgraph = context.evaluated_depsgraph_get()
bm = bmesh.new()
Expand Down Expand Up @@ -61,17 +50,20 @@ def bake(context, data, offsets, normals, size):
name="offsets",
width=width,
height=height,
alpha=True,
alpha=False,
float_buffer=True
)
normal_texture = data.images.new(
name="normals",
width=width,
height=height,
alpha=True
alpha=False
)
offset_texture.pixels = offsets
normal_texture.pixels = normals

return offset_texture, normal_texture



def create_export_mesh_object(context, data, me):
Expand All @@ -96,43 +88,64 @@ class OBJECT_OT_VertexAnimationTexture(bpy.types.Operator):

#--- execute ---#
def execute(self, context):
start = context.scene.frame_start
end = context.scene.frame_end
time_range = end - start + 1
s = context.scene.start_frame
e = context.scene.end_frame
p = context.scene.export_path
time_range = e - s + 1
if not p:
print('[error] : export path is empty.')
return


origin_obj = context.selected_objects[0]

mesh_seq = create_mesh_sequence(context, bpy.data, origin_obj)

offsets, normals = create_vertex_sequences(bpy.data, mesh_seq)

texture_size = len(origin_obj.data.vertices), time_range
bake(context, bpy.data, offsets, normals, texture_size)

# create offsets & normal textures---------------------------------
mesh_seq = create_mesh_sequence(context, bpy.data, origin_obj)
offsets, normals = create_vertex_sequences(bpy.data, mesh_seq)
offset_texture, normal_texture = bake(context, bpy.data, offsets, normals, texture_size)

# create mesh object with custom attribute---------------------------------
origin = mesh_seq[0].copy()
create_export_mesh_object(context, bpy.data, origin)

mobj = create_export_mesh_object(context, bpy.data, origin)

# export assets---------------------------------
origin_obj.select_set(False)
mobj.select_set(True)
bpy.ops.export_scene.fbx(filepath=p + "model.fbx", use_selection=True, global_scale=0.01)

offset_texture.file_format = 'OPEN_EXR'
offset_texture.filepath_raw = p + "offsets.exr"
offset_texture.save()

# todo : save 16bit or 10bit / RGB format for memory friendly.
normal_texture.file_format = 'OPEN_EXR'
normal_texture.filepath_raw = p + "normals.exr"
normal_texture.save()


context.scene.frame_set(1)
return {'FINISHED'}


class VIEW3D_PT_VertexAnimationTexture(bpy.types.Panel):
bl_label = "Vertex Animation"
bl_label = "VAT Exporter"
bl_idname = "VIEW3D_PT_Vertex_Animation_Texture"
bl_category = "Touch Designer"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'

def draw(self, context):
layout = self.layout
layout.label(text="Export Vertex & Normal VAT")
# layout.label(text="Export Vertex & Normal VAT")
layout.use_property_split = True
layout.use_property_decorate = False

s = context.scene
col = layout.column(align=True)
col.prop(s, "frame_start", text="Frame Start")
col.prop(s, "frame_end", text="Frame End")
col.prop(s, "start_frame")
col.prop(s, "end_frame")
col.prop(s, 'export_path')
layout.operator(OBJECT_OT_VertexAnimationTexture.bl_idname, text = "Export")

#
Expand All @@ -147,19 +160,48 @@ def draw(self, context):
# register
#
def register():
for c in classs:
bpy.utils.register_class(c)
bpy.types.Scene.start_frame = bpy.props.IntProperty(
name = "Frame Start",
default = 1,
description = "Set start frame",
min = 1,
max = 4098
)
bpy.types.Scene.end_frame = bpy.props.IntProperty(
name = "Frame End",
default = 300,
description = "Set end frame",
min = 1,
max = 4096
)
bpy.types.Scene.export_path = bpy.props.StringProperty(
name = "Export Directory",
default = "",
description = "Set your export dir for fbx & vats",
subtype = 'DIR_PATH'
)

# for c in classs:
# bpy.utils.register_class(c)

#
# unregister
#
def unregister():
for c in classs:
bpy.utils.register_class(c)
del bpy.types.Scene.start_frame
del bpy.types.Scene.end_frame
del bpy.types.Scene.export_path

# for c in classs:
# bpy.utils.register_class(c)

#
# script entry
#
if __name__ == "__main__":
register()
# if __name__ == "__main__":
# register()


# comment out if you develop
# unregister()
# register()
Binary file removed readme/exr_setting.PNG
Binary file not shown.
Binary file removed readme/normal_setting.PNG
Binary file not shown.
Binary file modified readme/output.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added readme/panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 509e069

Please sign in to comment.