[CG Pipeline] Refactor

This commit is contained in:
brothermechanic 2023-11-13 13:07:33 +00:00 committed by Igor Brylyov
parent 6538f70d54
commit b3612d8655
23 changed files with 634 additions and 645 deletions

View file

@ -2,19 +2,20 @@
#!/usr/bin/env python
'''
DESCRIPTION.
Convert and setup FreeCAD solid objects to 3d assets.
Convert and setup FreeCAD scene to cg assets.
Support Blender compiled as a Python Module only!
'''
__version__ = '0.6'
__version__ = '0.7'
import collections
import json
import logging
import shutil
import os
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 blender.utils.collection_tools import (copy_collections_recursive,
remove_collections_with_objects)
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
@ -22,142 +23,159 @@ 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.texturing.bake_submitter import bw_submit
from blender.texturing.bake_submitter import bw_bake
from blender.texturing.composing import compose_baked_textures
from blender.texturing.shading import assign_pbr_material
from blender.export.dae import export_dae
from blender.export.stl import export_stl
from blender.export.fbx import export_fbx
import bpy
import mathutils
# TODO Path
freecad_to_json_script = 'freecad_to_json.py'
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
'''
IMPORT COLLECTIONS NAMIG CONVENTION:
Parts - collection for mesh objects
LCS - collection for location points
Hierarchy - collection for hierarchy locators
# TODO NIX
freecad_bin = 'freecadcmd'
# TODO NIX BakwWrangler/nodes/node_tree.py:659
blender_bin = 'blender'
LCS POINT'S SUFFIXES CONVENTION:
'_in' - inlet suffix
'_out' - outlet suffix
'_root' - root suffix
CG ASSETS SUFFIXES CONVENTION:
'_hp' - hightpoly asset (reference baking source)
'_lp' - lowpoly asset (prepared for game engines)
'_render' - root suffix (prepared for render engines)
'''
# ENV
freecadcmd = 'freecadcmd'
fcstd_data_script = 'freecad_to_json.py'
# 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'
midpoly = 'mp'
lowpoly = '_lp'
render = '_render'
# TODO WEBAPP
cg_config = {
'lcs_col_name': 'LCS',
'parts_col_name': 'Parts',
'midpoly_col_name': 'Midpoly',
'lowpoly_col_name': 'Lowpoly',
'lcs_inlet': 'in',
'lcs_outlet': 'out',
'lcs_root': 'root',
'hightpoly': 'hp',
'midpoly': 'mp',
'lowpoly': 'lp',
'render': 'render',
}
def cg_pipeline(**kwargs):
''' CG asset creation pipeline '''
assembly_name = kwargs['fcstd_path'].rpartition('/')[2].rpartition('.')[0]
# freecad don't like other paths
parts_sequence_path = kwargs.pop('parts_sequence_path', None)
blend_path = kwargs.pop('blend_path', None)
mesh_export_path = kwargs.pop('mesh_export_path', None)
config = kwargs.pop('config', None)
export_path = kwargs.pop('export_path', None)
# for eatch sequence
parts_sequence = None
if parts_sequence_path and os.path.isfile(parts_sequence_path):
with open(parts_sequence_path, 'r', encoding='utf-8') as sequence_file:
parts_sequence = json.load(sequence_file)
# output file management
if not blend_path:
blend_path = kwargs['fcstd_path'].replace('\\', '/').rpartition('/')[0]
if not export_path:
export_path = os.path.join(blend_path, 'assets').replace('\\', '/')
os.makedirs(blend_path, exist_ok=True)
# prepare blend file
remove_collections()
blend_file = os.path.join(blend_path, f'{assembly_name}.blend').replace('\\', '/')
remove_collections_with_objects()
cleanup_orphan_data()
# convert FreeCAD scene to Blender scene
# 1 convert FreeCAD scene to Blender scene
imported_objects = json_to_blend(
json.loads(
cmd_proc(freecadcmd,
fcstd_data_script,
'--',
**kwargs
).split('FreeCAD ')[0]
)
cmd_proc(freecad_bin,
freecad_to_json_script,
'--',
**kwargs
).split('FreeCAD ')[0]
), **cg_config
)
# 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))
bpy.ops.wm.save_as_mainfile(filepath=blend_path)
# Save original freecad setup as blender scene
bpy.ops.wm.save_as_mainfile(filepath=f'{blend_file.rpartition(".")[0]}_orig.blend')
# restructuring hierarchy by lcs points
# 1 prepare highpoly
part_names = None
lcs_pipeline = True
if imported_objects['objs_lcs']:
restruct_hierarchy(imported_objects['objs_lcs'])
part_names = restruct_hierarchy(
imported_objects['objs_lcs'], parts_sequence, **cg_config)
# non lcs pipeline
if not part_names:
lcs_pipeline = False
part_names = [[obj for obj in bpy.data.objects if not obj.parent][0].name]
# prepare highpoly
if imported_objects['objs_foreground']:
setup_meshes(imported_objects['objs_foreground'], sharpness=True, shading=True)
setup_meshes(imported_objects['objs_foreground'],
sharpness=True, shading=True)
else:
setup_meshes(imported_objects['objs_background'], sharpness=True, shading=True)
setup_meshes(imported_objects['objs_background'],
sharpness=True, shading=True)
# TODO Part's names from LCS names?
part_names = [lcs_name.split(inlet)[0]
for lcs_name in imported_objects['objs_lcs']
if lcs_name.endswith(inlet)]
# prepare midpoly
copy_collections_recursive(
bpy.data.collections[parts_col_name], suffix=midpoly
# 2 prepare midpoly
copy_col_name = copy_collections_recursive(
bpy.data.collections[cg_config['parts_col_name']],
suffix=cg_config['midpoly']
)
hightpoly_collections_to_midpoly(part_names)
midpoly_obj_names = hightpoly_collections_to_midpoly(
copy_col_name, part_names, lcs_pipeline, **cg_config)
# prepare lowpoly
lowpoly_obj_names = parts_to_shells(part_names)
# 3 prepare lowpoly
lowpoly_obj_names = parts_to_shells(part_names, lcs_pipeline, **cg_config)
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))
bpy.ops.wm.save_as_mainfile(filepath=blend_path)
# Save before baking
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
# bake textures
bpy.ops.wm.open_mainfile(filepath=blend_path)
textures_path = bw_submit(lowpoly_obj_names)
compose_baked_textures(textures_path)
assign_pbr_material(lowpoly_obj_names, textures_path)
# 4 bake textures
if kwargs['textures_resolution'] != 0:
textures_path = os.path.join(blend_path, 'textures').replace('\\', '/')
bake_paths = bw_bake(lowpoly_obj_names,
textures_path,
kwargs['textures_resolution'],
**cg_config)
# Save baking result
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
# 5 prepare textures
bpy.ops.wm.quit_blender()
compose_baked_textures(textures_path, bake_paths, kwargs['textures_resolution'])
for bake_path in bake_paths:
shutil.rmtree(bake_path)
bpy.ops.wm.open_mainfile(filepath=blend_file)
assign_pbr_material(lowpoly_obj_names, textures_path)
bpy.ops.file.make_paths_relative()
# Save assigned lowpoly assets
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
for part_name in part_names:
export_fbx(
obj_name=f'{part_name}_{cg_config["lowpoly"]}',
path=export_path,
subdir='fbx')
# export object meshes and urdf
# 6 export object meshes and urdf
to_urdf = collections.defaultdict(list)
if lowpoly_obj_names:
export_obj_names = lowpoly_obj_names
else:
export_obj_names = sum([imported_objects['objs_foreground'],
imported_objects['objs_background']], [])
link = {}
for export_obj_name in export_obj_names:
for part_name in part_names:
link_prop = {}
if mesh_export_path is not None:
link_prop['visual'] = export_dae(
obj_name=export_obj_name, path=mesh_export_path, subdir='visual')
link_prop['collision'] = export_stl(
obj_name=export_obj_name, path=mesh_export_path, subdir='collision')
link[export_obj_name] = link_prop
link_prop['visual'] = export_dae(
obj_name=f'{part_name}_{cg_config["midpoly"]}',
path=export_path,
subdir='dae')
link_prop['collision'] = export_stl(
obj_name=f'{part_name}_{cg_config["lowpoly"]}',
path=export_path,
subdir='collision')
link[part_name] = link_prop
to_urdf['links'].append(link)
#config = {'sequences': [['cube1', 'cube2', 'cube3', 'cube4'], ['cube2', 'cube1', 'cube4', 'cube3']]}
# TODO export urdf
config = kwargs.pop('config', None)
# config = {'sequences': [['cube1', 'cube2', 'cube3', 'cube4'], ['cube2', 'cube1', 'cube4', 'cube3']]}
if config:
for sequence in config['sequences']:
joint = {}
@ -185,6 +203,9 @@ def cg_pipeline(**kwargs):
print(json.dumps(to_urdf, indent=4))
logger.info('%s original hightpoly collections ready!', len(part_names))
logger.info('%s midpoly objects ready!', len(midpoly_obj_names))
logger.info('%s lowpoly objects ready!', len(lowpoly_obj_names))
if __name__ == '__main__':
import argparse
@ -241,21 +262,33 @@ if __name__ == '__main__':
required=False
)
parser.add_argument(
'--mesh_export_path',
type=str, help='Path for export meshes',
'--parts_sequence_path',
type=str, help='Path to parts assembling sequence json file.',
required=False
)
parser.add_argument(
'--export_path',
type=str, help='Path for export assets. If not, fcstd_path will be used instead.',
required=False
)
parser.add_argument(
'--blend_path',
type=str,
help='Path for export blend assembly file',
help='Path for blender scene. If not, fcstd_path will be used instead.',
required=False
)
parser.add_argument(
'--textures_resolution',
type=int,
help='Set baking texture resolution. Recomended - 4096 pix ',
default=512,
required=False
)
args = parser.parse_args()
kwargs = {key: getattr(args, key) for key in dir(args) if not key.startswith('_')}
cg_kwargs = {key: getattr(args, key) for key in dir(args) if not key.startswith('_')}
cg_pipeline(**kwargs)
cg_pipeline(**cg_kwargs)
logger.info('CG Pipeline Completed!')