From 25c9cbfbe984c16093e41e27f8b427dd2adddc56 Mon Sep 17 00:00:00 2001 From: brothermechanic Date: Mon, 16 Oct 2023 12:21:34 +0000 Subject: [PATCH] [Blender] Baking optimization --- cg/blender/export/__init__.py | 1 + cg/blender/processing/midpoly_setup.py | 78 ++++++++++++++++++++++++++ cg/blender/processing/uv_setup.py | 1 + cg/blender/utils/collection_tools.py | 57 +++++++++++++++++++ cg/blender/utils/mesh_tools.py | 20 +++++++ cg/pipeline/cg_pipeline.py | 38 ++++++++----- 6 files changed, 182 insertions(+), 13 deletions(-) create mode 100644 cg/blender/processing/midpoly_setup.py create mode 100644 cg/blender/utils/collection_tools.py diff --git a/cg/blender/export/__init__.py b/cg/blender/export/__init__.py index c5c9e01..438e036 100644 --- a/cg/blender/export/__init__.py +++ b/cg/blender/export/__init__.py @@ -26,6 +26,7 @@ logging.basicConfig(level=logging.INFO) def export_decorator(func): def wrapper(**kwargs): + bpy.ops.object.select_all(action='DESELECT') # add defaults kwargs.setdefault('path', '//') kwargs.setdefault('subdir', '') diff --git a/cg/blender/processing/midpoly_setup.py b/cg/blender/processing/midpoly_setup.py new file mode 100644 index 0000000..6127742 --- /dev/null +++ b/cg/blender/processing/midpoly_setup.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2023 Ilia Kurochkin +# +# 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 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +''' +DESCRIPTION. +Basic mesh processing for asset pipeline. +''' +__version__ = '0.1' + +import logging +import bpy +import math + +from blender.utils.object_relations import parenting +from blender.utils.remove_collections import remove_collections +from blender.utils.mesh_tools import collect_less_volume_objs + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + + +# COLLECTIONS NAMIG CONVENTION +parts_col_name = 'Parts' +lcs_col_name = 'LCS' +hierarchy_col_name = 'Hierarchy' +midpoly_col_name = 'Midpoly' +lowpoly_col_name = 'Lowpoly' +# LCS POINT'S SUFFIXES CONVENTION +inlet = '_in' +outlet = '_out' +root = '_root' +# CG ASSETS SUFFIXES CONVENTION +hightpoly = '_hp' +midpoly = 'mp' +lowpoly = '_lp' +render = '_render' + + +def hightpoly_collections_to_midpoly(part_names): + ''' Convert part's collecttions to single objects. ''' + for part_name in part_names: + midpoly_name = '_'.join((part_name, midpoly)) + midpoly_mesh = bpy.data.meshes.new(midpoly_name) + midpoly_obj = bpy.data.objects.new(midpoly_name, midpoly_mesh) + bpy.context.view_layer.update() + part_inlet = bpy.data.objects.get('{}{}'.format(part_name, inlet)) + midpoly_obj.matrix_world = part_inlet.matrix_world.copy() + parenting(part_inlet, midpoly_obj) + midpoly_parts_col = bpy.data.collections['_'.join((parts_col_name, midpoly))] + midpoly_parts_col.objects.link(midpoly_obj) + for col in midpoly_parts_col.children.keys(): + if part_name in col: + bpy.ops.object.select_all(action='DESELECT') + exclude_objs = collect_less_volume_objs( + bpy.data.collections[col].objects, min_volume=2.0e-06) + for obj in bpy.data.collections[col].objects: + if obj not in exclude_objs: + obj.select_set(state=True) + midpoly_obj.select_set(state=True) + bpy.context.view_layer.objects.active = midpoly_obj + bpy.ops.object.join() + bpy.ops.object.shade_smooth(use_auto_smooth=True) + break + + midpoly_parts_col.name = midpoly_col_name + for col in midpoly_parts_col.children.keys(): + remove_collections(col) + + return logger.info('Setup of %s midpoly meshes is finished!', len(part_names)) diff --git a/cg/blender/processing/uv_setup.py b/cg/blender/processing/uv_setup.py index ccb720d..f2ddb93 100644 --- a/cg/blender/processing/uv_setup.py +++ b/cg/blender/processing/uv_setup.py @@ -28,6 +28,7 @@ def uv_unwrap(obj_names, angle_limit=30): ''' UV unwrapping and UV packing processing ''' for obj_name in obj_names: obj = bpy.data.objects[obj_name] + bpy.ops.object.select_all(action='DESELECT') obj.select_set(True) bpy.context.view_layer.objects.active = obj bpy.ops.object.mode_set(mode='EDIT') diff --git a/cg/blender/utils/collection_tools.py b/cg/blender/utils/collection_tools.py new file mode 100644 index 0000000..1be59c3 --- /dev/null +++ b/cg/blender/utils/collection_tools.py @@ -0,0 +1,57 @@ +# coding: utf-8 +# Copyright (C) 2023 Ilia Kurochkin +# +# 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 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +''' +DESCRIPTION. +Various collection tools. +''' +__version__ = '0.1' + +import bpy +from collections import defaultdict + + +def copy_objects(from_col, to_col, linked, double_lut): + '''Function copying objects from collection to collection.''' + for obj in from_col.objects: + double = obj.copy() + if not linked and obj.data: + double.data = double.data.copy() + to_col.objects.link(double) + double_lut[obj] = double + return True + + +def copy_collections_recursive(collection, suffix='copy', linked=False): + '''Function recursive copying collection.''' + double_lut = defaultdict(lambda: None) + parent = [p for p in (bpy.data.collections[:] + [bpy.context.scene.collection]) + if collection.name in p.children.keys()][0] + + def _copy(parent, collection, suffix, linked=False): + '''Function copying collection.''' + clone_collection = bpy.data.collections.new( + '_'.join((collection.name, suffix)) + ) + copy_objects(collection, clone_collection, linked, double_lut) + + for _collection in collection.children: + _copy(clone_collection, _collection, suffix, linked) + + parent.children.link(clone_collection) + + _copy(parent, collection, suffix, linked) + for obj, double in tuple(double_lut.items()): + parent = double_lut[obj.parent] + if parent: + double.parent = parent + return True diff --git a/cg/blender/utils/mesh_tools.py b/cg/blender/utils/mesh_tools.py index 9cffcb7..c2b6221 100644 --- a/cg/blender/utils/mesh_tools.py +++ b/cg/blender/utils/mesh_tools.py @@ -16,6 +16,7 @@ Various mesh tools for Edit Mode. ''' __version__ = '0.1' +import bpy import bmesh from math import radians @@ -81,3 +82,22 @@ def select_stratched_edges(me, edge_length_limit=0.002): edge_max.select_set(True) bmesh.update_edit_mesh(me) return me + + +def collect_less_volume_objs(objs: list, min_volume): + ''' Separate selection for less volume objects. ''' + less_volume_objs = [] + for obj in objs: + bpy.ops.object.select_all(action='DESELECT') + if obj.type != 'MESH': + continue + # requed for bmesh + obj.hide_set(False) + obj.select_set(state=True) + if obj.type == 'MESH': + bpy.ops.object.mode_set(mode='EDIT') + bm = bmesh.from_edit_mesh(obj.data) + if bm.calc_volume() < min_volume: + less_volume_objs.append(obj) + bpy.ops.object.mode_set(mode='OBJECT') + return less_volume_objs diff --git a/cg/pipeline/cg_pipeline.py b/cg/pipeline/cg_pipeline.py index 942a117..44a211a 100644 --- a/cg/pipeline/cg_pipeline.py +++ b/cg/pipeline/cg_pipeline.py @@ -14,10 +14,12 @@ from itertools import zip_longest from blender.utils.remove_collections import remove_collections from blender.utils.cleanup_orphan_data import cleanup_orphan_data +from blender.utils.collection_tools import copy_collections_recursive from utils.cmd_proc import cmd_proc from blender.import_cad.build_blender_scene import json_to_blend from blender.processing.restruct_hierarchy_by_lcs import restruct_hierarchy from blender.processing.highpoly_setup import setup_meshes +from blender.processing.midpoly_setup import hightpoly_collections_to_midpoly from blender.processing.lowpoly_setup import parts_to_shells from blender.processing.uv_setup import uv_unwrap from blender.export.dae import export_dae @@ -59,6 +61,7 @@ outlet = '_out' root = '_root' # CG ASSETS SUFFIXES CONVENTION hightpoly = '_hp' +midpoly = 'mp' lowpoly = '_lp' render = '_render' @@ -68,6 +71,7 @@ def cg_pipeline(**kwargs): blend_path = kwargs.pop('blend_path', None) mesh_export_path = kwargs.pop('mesh_export_path', None) + config = kwargs.pop('config', None) # prepare blend file remove_collections() @@ -88,7 +92,7 @@ def cg_pipeline(**kwargs): if imported_objects['objs_lcs']: restruct_hierarchy(imported_objects['objs_lcs']) - # save blender scene + # save import in blender scene if blend_path is not None: if not os.path.isdir(os.path.dirname(blend_path)): os.makedirs(os.path.dirname(blend_path)) @@ -105,11 +109,18 @@ def cg_pipeline(**kwargs): for lcs_name in imported_objects['objs_lcs'] if lcs_name.endswith(inlet)] - # prepare lowpoly - lowpoly_objs = parts_to_shells(part_names) - uv_unwrap(lowpoly_objs) + # prepare midpoly + copy_collections_recursive( + bpy.data.collections[parts_col_name], suffix=midpoly + ) + hightpoly_collections_to_midpoly(part_names) - # save blender scene + + # prepare lowpoly + lowpoly_obj_names = parts_to_shells(part_names) + uv_unwrap(lowpoly_obj_names) + + # save lowpoly in blender scene if blend_path is not None: if not os.path.isdir(os.path.dirname(blend_path)): os.makedirs(os.path.dirname(blend_path)) @@ -118,29 +129,30 @@ def cg_pipeline(**kwargs): # export object meshes and urdf to_urdf = collections.defaultdict(list) - if lowpoly_objs: - export_objs = lowpoly_objs + if lowpoly_obj_names: + export_obj_names = lowpoly_obj_names else: - export_objs = sum([imported_objects['objs_foreground'], + export_obj_names = sum([imported_objects['objs_foreground'], imported_objects['objs_background']], []) link = {} - for export_obj in export_objs: + for export_obj_name in export_obj_names: link_prop = {} if mesh_export_path is not None: link_prop['visual'] = export_dae( - obj_name=export_obj, path=mesh_export_path, subdir='visual') + obj_name=export_obj_name, path=mesh_export_path, subdir='visual') link_prop['collision'] = export_stl( - obj_name=export_obj, path=mesh_export_path, subdir='collision') + obj_name=export_obj_name, path=mesh_export_path, subdir='collision') - link[export_obj] = link_prop + link[export_obj_name] = link_prop to_urdf['links'].append(link) - config = {'sequences': [['cube1', 'cube2', 'cube3', 'cube4'], ['cube2', 'cube1', 'cube4', 'cube3']]} + #config = {'sequences': [['cube1', 'cube2', 'cube3', 'cube4'], ['cube2', 'cube1', 'cube4', 'cube3']]} if config: for sequence in config['sequences']: joint = {} + # TODO collect pairs 0_1, 1_2, 2_3, 3_4, ... for pair in zip_longest(sequence[0::2], sequence[1::2]): joint_prop = {} if pair[1]: