2023-09-12 09:07:30 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (C) 2023 Ilia Kurochkin <brothermechanic@gmail.com>
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
Create lowpoly shells from parts collections.
|
|
|
|
'''
|
|
|
|
__version__ = '0.1'
|
|
|
|
|
|
|
|
import logging
|
|
|
|
import bpy
|
|
|
|
|
|
|
|
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__)
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
|
|
|
|
|
|
|
# COLLECTIONS NAMIG CONVENTION
|
|
|
|
parts_col_name = 'Parts'
|
|
|
|
lcs_col_name = 'LCS'
|
|
|
|
hierarchy_col_name = 'Hierarchy'
|
|
|
|
lowpoly_col_name = 'Lowpoly'
|
|
|
|
# LCS POINT'S SUFFIXES CONVENTION
|
|
|
|
inlet = '_in'
|
|
|
|
outlet = '_out'
|
|
|
|
root = '_root'
|
|
|
|
# CG ASSETS SUFFIXES CONVENTION
|
|
|
|
hightpoly = '_hp'
|
|
|
|
lowpoly = '_lp'
|
|
|
|
render = '_render'
|
|
|
|
|
|
|
|
|
|
|
|
def parts_to_shells(hightpoly_part_names):
|
|
|
|
''' Create lowpoly shells from parts collections. '''
|
|
|
|
logger.info('Lowpoly shells creation launched...')
|
|
|
|
|
|
|
|
lowpoly_col = bpy.data.collections.new(lowpoly_col_name)
|
|
|
|
bpy.context.scene.collection.children.link(lowpoly_col)
|
|
|
|
|
|
|
|
for part_name in hightpoly_part_names:
|
|
|
|
# generate lowpoly objects from part collections
|
|
|
|
lowpoly_name = ('{}{}'.format(part_name, lowpoly))
|
|
|
|
lowpoly_mesh = bpy.data.meshes.new(lowpoly_name)
|
|
|
|
lowpoly_obj = bpy.data.objects.new(lowpoly_name, lowpoly_mesh)
|
|
|
|
bpy.context.view_layer.update()
|
|
|
|
part_inlet = bpy.data.objects.get('{}{}'.format(part_name, inlet))
|
|
|
|
lowpoly_obj.matrix_world = part_inlet.matrix_world.copy()
|
|
|
|
parenting(part_inlet, lowpoly_obj)
|
|
|
|
lowpoly_col.objects.link(lowpoly_obj)
|
|
|
|
|
|
|
|
shell_remesher(lowpoly_obj, 'remesh_nodes', 'robossembler')
|
|
|
|
part_col = bpy.data.collections[('{}{}'.format(part_name, hightpoly))]
|
|
|
|
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(part_inlet, 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')
|
|
|
|
|
2023-10-10 16:32:29 +00:00
|
|
|
logger.info('Generation of %s lowpoly shells is finished!', len(lowpoly_col.objects))
|
2023-09-12 09:07:30 +00:00
|
|
|
|
2023-10-10 16:32:29 +00:00
|
|
|
return [obj.name for obj in lowpoly_col.objects]
|