Add mesh baker
This commit is contained in:
		
							parent
							
								
									23faf4f2a8
								
							
						
					
					
						commit
						08954dd7d2
					
				
					 4 changed files with 268 additions and 0 deletions
				
			
		
							
								
								
									
										8
									
								
								mesh_baking/Makefile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								mesh_baking/Makefile
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
all: mesh_baking.zip
 | 
			
		||||
 | 
			
		||||
%.zip: *.py
 | 
			
		||||
	zip -r $@ $^
 | 
			
		||||
 | 
			
		||||
.PHONY: clean
 | 
			
		||||
clean:
 | 
			
		||||
	${RM} *.zip
 | 
			
		||||
							
								
								
									
										33
									
								
								mesh_baking/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								mesh_baking/__init__.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
# 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" : "Mesh Baking",
 | 
			
		||||
    "author" : "S",
 | 
			
		||||
    "description" : "",
 | 
			
		||||
    "blender" : (2, 80, 0),
 | 
			
		||||
    "version" : (0, 0, 1),
 | 
			
		||||
    "location" : "",
 | 
			
		||||
    "warning" : "",
 | 
			
		||||
    "category" : "Generic"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
from . import auto_load
 | 
			
		||||
 | 
			
		||||
auto_load.init()
 | 
			
		||||
 | 
			
		||||
def register():
 | 
			
		||||
    auto_load.register()
 | 
			
		||||
 | 
			
		||||
def unregister():
 | 
			
		||||
    auto_load.unregister()
 | 
			
		||||
							
								
								
									
										157
									
								
								mesh_baking/auto_load.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								mesh_baking/auto_load.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										70
									
								
								mesh_baking/tool.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								mesh_baking/tool.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
import bpy
 | 
			
		||||
from bpy.types import Context, Object, Scene, Collection
 | 
			
		||||
 | 
			
		||||
def get_first_collection_sharing_scene(scene: Scene, obj: Object):
 | 
			
		||||
    def collection_in_scene(collection: Collection, tree: Collection):
 | 
			
		||||
        if collection == tree:
 | 
			
		||||
            return True
 | 
			
		||||
        return any(collection_in_scene(collection, child) for child in tree.children)
 | 
			
		||||
    return next(candidate for candidate in obj.users_collection if collection_in_scene(candidate, scene.collection))
 | 
			
		||||
 | 
			
		||||
def is_pc2_enabled():
 | 
			
		||||
    return hasattr(bpy.ops, "export_shape") and hasattr(bpy.ops.export_shape, "pc2")
 | 
			
		||||
 | 
			
		||||
def export_pc2_filepath(obj: Object, absolute=False):
 | 
			
		||||
    path = f"//{obj.name}.pc2"
 | 
			
		||||
    return bpy.path.abspath(path) if absolute else path
 | 
			
		||||
 | 
			
		||||
def export_pc2(scene: Scene, obj: Object):
 | 
			
		||||
    path = export_pc2_filepath(obj, absolute=True)
 | 
			
		||||
    frame_start = scene.frame_start
 | 
			
		||||
    scene.frame_set(frame_start)
 | 
			
		||||
    frame_end = scene.frame_end
 | 
			
		||||
    bpy.context.view_layer.objects.active = obj
 | 
			
		||||
    bpy.context.view_layer.update()
 | 
			
		||||
    bpy.ops.export_shape.pc2(filepath=path, check_existing=False, rot_x90=False, world_space=False, apply_modifiers=True, range_start=frame_start, range_end=frame_end, sampling='1')
 | 
			
		||||
    bpy.context.view_layer.update()
 | 
			
		||||
    frame_end = scene.frame_end
 | 
			
		||||
    scene.frame_set(frame_start)
 | 
			
		||||
    bpy.context.view_layer.update()
 | 
			
		||||
    for modifier in obj.modifiers:
 | 
			
		||||
        bpy.ops.object.modifier_apply(modifier=modifier.name)
 | 
			
		||||
        bpy.context.view_layer.update()
 | 
			
		||||
 | 
			
		||||
def create_proxy(scene: Scene, obj: Object):
 | 
			
		||||
    copy = obj.copy()
 | 
			
		||||
    copy.name = f"{obj.name}Bake"
 | 
			
		||||
    copy.data = copy.data.copy()
 | 
			
		||||
    copy.constraints.clear()
 | 
			
		||||
    c = get_first_collection_sharing_scene(scene, obj)
 | 
			
		||||
    c.objects.link(copy)
 | 
			
		||||
    export_pc2(scene, copy)
 | 
			
		||||
    mod = copy.modifiers.new("Mesh Bake", "MESH_CACHE")
 | 
			
		||||
    mod.cache_format = "PC2"
 | 
			
		||||
    mod.filepath = export_pc2_filepath(obj)
 | 
			
		||||
 | 
			
		||||
class BakeMeshOperator(bpy.types.Operator):
 | 
			
		||||
    bl_idname = "object.bakemesh"
 | 
			
		||||
    bl_label = "Bake Mesh to PC2"
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def poll(Cls, context: Context):
 | 
			
		||||
        return context.active_object is not None
 | 
			
		||||
 | 
			
		||||
    def execute(self, context: Context):
 | 
			
		||||
        if not is_pc2_enabled():
 | 
			
		||||
            self.report({"ERROR"}, "PC2 export addon must be enabled")
 | 
			
		||||
            return {'FINISHED'}
 | 
			
		||||
        scene = context.scene
 | 
			
		||||
        obj = context.active_object
 | 
			
		||||
        create_proxy(scene, obj)
 | 
			
		||||
        return {'FINISHED'}
 | 
			
		||||
 | 
			
		||||
def BakeMeshOperator_menu_func(self, context: Context):
 | 
			
		||||
    return self.layout.operator(BakeMeshOperator.bl_idname)
 | 
			
		||||
 | 
			
		||||
def register():
 | 
			
		||||
    bpy.types.VIEW3D_MT_object.append(BakeMeshOperator_menu_func)
 | 
			
		||||
 | 
			
		||||
def unregister():
 | 
			
		||||
    bpy.types.VIEW3D_MT_object.remove(BakeMeshOperator_menu_func)
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue