# 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. Retopology visual assets for simulation pipeline. ''' __version__ = '0.3' import logging import bpy import math from blender.utils.generative_modifiers import shell_remesher from blender.utils.object_converter import mesh_to_mesh from blender.utils.object_relations import parenting from blender.utils.mesh_tools import select_peaks, select_stratched_edges logger = logging.getLogger(__name__) def parts_to_shells(part_names, **cg_config): ''' Create lowpoly shells from parts collections. ''' logger.info('Lowpoly shells creation launched...') lowpoly_col = bpy.data.collections.new(cg_config['lowpoly_col_name']) bpy.context.scene.collection.children.link(lowpoly_col) for part_name in part_names: # generate lowpoly objects from part collections lowpoly_name = '{}_{}'.format(part_name, cg_config['lowpoly']) lowpoly_mesh = bpy.data.meshes.new(lowpoly_name) lowpoly_obj = bpy.data.objects.new(lowpoly_name, lowpoly_mesh) bpy.context.view_layer.update() lowpoly_col.objects.link(lowpoly_obj) if bpy.data.objects[part_name].parent: root_locator = bpy.data.objects[part_name].parent else: root_locator = bpy.data.objects[part_name] lowpoly_obj.matrix_world = root_locator.matrix_world.copy() parenting(root_locator, lowpoly_obj) part_col = bpy.data.collections[ '{}_{}'.format(part_name, cg_config['hightpoly'])] shell_remesher(lowpoly_obj, 'remesh_nodes', 'robossembler') lowpoly_obj.modifiers['remesh_nodes']['Input_0'] = part_col remesh_voxel = lowpoly_obj.modifiers.new('remesh_voxel', type='REMESH') remesh_voxel.mode = 'VOXEL' remesh_voxel.voxel_size = 0.001 remesh_sharp = lowpoly_obj.modifiers.new('remesh_sharp', type='REMESH') remesh_sharp.mode = 'SHARP' remesh_sharp.octree_depth = 7 decimate = lowpoly_obj.modifiers.new('decimate', type='DECIMATE') decimate.decimate_type = 'COLLAPSE' decimate.ratio = 0.1 # apply all modifiers to mesh parenting(root_locator, mesh_to_mesh(lowpoly_obj)) # fix non_manifold shape for lowpoly_obj in lowpoly_col.objects: bpy.ops.object.select_all(action='DESELECT') lowpoly_obj.select_set(state=True) bpy.context.view_layer.objects.active = lowpoly_obj bpy.ops.object.mode_set(mode='EDIT') # pass 1 bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') select_peaks(lowpoly_obj.data) bpy.ops.mesh.select_non_manifold() bpy.ops.mesh.dissolve_mode(use_verts=True, use_boundary_tear=False) bpy.ops.mesh.delete(type='VERT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') # pass 2 bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') select_peaks(lowpoly_obj.data) bpy.ops.mesh.select_non_manifold() bpy.ops.mesh.dissolve_mode(use_verts=True, use_boundary_tear=False) bpy.ops.mesh.delete(type='VERT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') # pass 3 bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') select_peaks(lowpoly_obj.data) bpy.ops.mesh.select_non_manifold() bpy.ops.mesh.dissolve_mode(use_verts=True, use_boundary_tear=False) bpy.ops.mesh.delete(type='VERT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') # pass 4 bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') select_peaks(lowpoly_obj.data) bpy.ops.mesh.select_non_manifold() bpy.ops.mesh.dissolve_mode(use_verts=True, use_boundary_tear=False) bpy.ops.mesh.delete(type='VERT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') # pass 5 bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.mesh.select_mode(type='EDGE') select_stratched_edges(lowpoly_obj.data) bpy.ops.mesh.dissolve_mode(use_verts=True) bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') bpy.ops.mesh.normals_make_consistent() # final bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.mesh.select_mode(type='FACE') bpy.ops.object.mode_set(mode='OBJECT') # shading bpy.ops.object.shade_smooth(use_auto_smooth=True) lowpoly_obj.data.auto_smooth_angle = math.radians(10) lowpoly_obj.modifiers.new(type='WEIGHTED_NORMAL', name='WeightedNormal') lowpoly_obj.modifiers['WeightedNormal'].keep_sharp = True bpy.ops.object.modifier_apply(modifier="WeightedNormal") logger.info('Generation of %s lowpoly shells is finished!', len(lowpoly_col.objects)) return [obj.name for obj in lowpoly_col.objects]