CG Pipelines: User interface refactor
This commit is contained in:
parent
e305d486f2
commit
021e5862ff
7 changed files with 431 additions and 280 deletions
|
@ -2,11 +2,10 @@
|
|||
#!/usr/bin/env python
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Convert and setup scene from FreeCAD data.
|
||||
CG asset (models) creation pipelines from FreeCAD data.
|
||||
Support Blender compiled as a Python Module only!
|
||||
'''
|
||||
__version__ = '0.7'
|
||||
import collections
|
||||
__version__ = '0.8'
|
||||
import json
|
||||
import logging
|
||||
import shutil
|
||||
|
@ -18,7 +17,9 @@ 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
|
||||
from blender.processing.restruct_hierarchy import (hierarchy_assembly,
|
||||
hierarchy_separated_parts,
|
||||
hierarchy_single_part)
|
||||
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
|
||||
|
@ -29,6 +30,7 @@ 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
|
||||
from blender.export.ply import export_ply
|
||||
import bpy
|
||||
|
||||
# TODO Path
|
||||
|
@ -57,34 +59,36 @@ cg_config = {
|
|||
'render': 'render',
|
||||
}
|
||||
|
||||
defined_pipeline_list = ['cad', 'highpoly', 'midpoly', 'lowpoly', 'baking', 'export']
|
||||
|
||||
|
||||
def cg_pipeline(**kwargs):
|
||||
''' CG asset creation pipeline '''
|
||||
assembly_name = kwargs['fcstd_path'].rpartition('/')[2].rpartition('.')[0]
|
||||
# freecad don't like other paths
|
||||
''' CG assets (models) creation pipeline '''
|
||||
# set defaults
|
||||
# ______________________________
|
||||
assembly_name = (
|
||||
kwargs['fcstd_path'].replace('\\', '/').rpartition('/')[2].rpartition('.')[0])
|
||||
# clear other paths from kwargs (freecad issue)
|
||||
parts_sequence_path = kwargs.pop('parts_sequence_path', None)
|
||||
blend_path = kwargs.pop('blend_path', 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)
|
||||
if kwargs['pipeline_type'] not in defined_pipeline_list:
|
||||
return logger.error('Pipeline type %s is not defined!', kwargs['pipeline_type'])
|
||||
|
||||
# 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('\\', '/')
|
||||
export_path = os.path.join(blend_path, 'models').replace('\\', '/')
|
||||
os.makedirs(blend_path, exist_ok=True)
|
||||
|
||||
# prepare blend file
|
||||
blend_file = os.path.join(blend_path, f'{assembly_name}.blend').replace('\\', '/')
|
||||
#blend_file = os.path.join(blend_path, f'{assembly_name}.blend').replace('\\', '/')
|
||||
remove_collections_with_objects()
|
||||
cleanup_orphan_data()
|
||||
|
||||
# 0 сonvert FreeCAD scene to Blender scene
|
||||
imported_objects = json_to_blend(
|
||||
# 1) сonvert FreeCAD scene to Blender scene
|
||||
# ______________________________
|
||||
cad_objects = json_to_blend(
|
||||
json.loads(
|
||||
cmd_proc(freecad_bin,
|
||||
freecad_to_json_script,
|
||||
|
@ -94,51 +98,119 @@ def cg_pipeline(**kwargs):
|
|||
), **cg_config
|
||||
)
|
||||
|
||||
# Save original freecad setup as blender scene
|
||||
bpy.ops.wm.save_as_mainfile(filepath=f'{blend_file.rpartition(".")[0]}_orig.blend')
|
||||
# Save original cad setup as blender scene
|
||||
if kwargs['pipeline_type'] == 'cad':
|
||||
blend_file = os.path.join(
|
||||
blend_path,
|
||||
'{}_{}.blend'.format(assembly_name, kwargs['pipeline_type'])
|
||||
).replace('\\', '/')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
|
||||
return blend_file
|
||||
|
||||
# 1 prepare highpoly
|
||||
part_names = None
|
||||
lcs_pipeline = True
|
||||
if 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]
|
||||
|
||||
if imported_objects['objs_foreground']:
|
||||
setup_meshes(imported_objects['objs_foreground'],
|
||||
sharpness=True, shading=True)
|
||||
# 2) prepare highpoly (depend of cad_objects['objs_lcs'] and parts_sequence)
|
||||
# ______________________________
|
||||
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)
|
||||
else:
|
||||
setup_meshes(imported_objects['objs_background'],
|
||||
parts_sequence = None
|
||||
lcs_inlet_objects = [
|
||||
inlet for inlet in cad_objects['objs_lcs']
|
||||
if inlet.endswith(cg_config['lcs_inlet'])]
|
||||
|
||||
# input cases
|
||||
# 1 case
|
||||
if parts_sequence and len(lcs_inlet_objects) > 1:
|
||||
logger.info('Parts assembling sequence and LCS points found! '
|
||||
'Launch "hierarchy_assembly" restructuring pipeline.')
|
||||
part_names = hierarchy_assembly(
|
||||
cad_objects['objs_lcs'], parts_sequence, **cg_config)
|
||||
# 2 case
|
||||
elif parts_sequence and len(lcs_inlet_objects) < 2:
|
||||
return logger.error('Assembly do not have enough LCS points!')
|
||||
# 3 case
|
||||
elif not parts_sequence and lcs_inlet_objects:
|
||||
logger.info('Parts assembling sequence not found! '
|
||||
'Launch "hierarchy_separated_parts" restructuring pipeline.')
|
||||
part_names = hierarchy_separated_parts(
|
||||
cad_objects['objs_lcs'], **cg_config)
|
||||
# 4 case
|
||||
elif not parts_sequence and not lcs_inlet_objects:
|
||||
logger.info('Parts assembling sequence and LCS points not found! '
|
||||
'Launch "hierarchy_single_part" restructuring pipeline.')
|
||||
part_names = hierarchy_single_part(**cg_config)
|
||||
|
||||
if not part_names:
|
||||
return logger.error('Can not generate parts!')
|
||||
|
||||
# setup highpolys with materials only
|
||||
if cad_objects['objs_foreground']:
|
||||
setup_meshes(cad_objects['objs_foreground'],
|
||||
sharpness=True, shading=True)
|
||||
# setup all highpolys
|
||||
else:
|
||||
setup_meshes(cad_objects['objs_background'],
|
||||
sharpness=True, shading=True)
|
||||
|
||||
# 2 prepare midpoly
|
||||
copy_col_name = copy_collections_recursive(
|
||||
# Save highpoly setup as blender scene
|
||||
if kwargs['pipeline_type'] == 'highpoly':
|
||||
blend_file = os.path.join(
|
||||
blend_path,
|
||||
'{}_{}.blend'.format(assembly_name, kwargs['pipeline_type'])
|
||||
).replace('\\', '/')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
|
||||
logger.info('%s original hightpoly collections ready!', len(part_names))
|
||||
return blend_file
|
||||
|
||||
# 3) prepare midpoly
|
||||
# ______________________________
|
||||
tmp_col_name = copy_collections_recursive(
|
||||
bpy.data.collections[cg_config['parts_col_name']],
|
||||
suffix=cg_config['midpoly']
|
||||
)
|
||||
midpoly_obj_names = hightpoly_collections_to_midpoly(
|
||||
copy_col_name, part_names, lcs_pipeline, **cg_config)
|
||||
tmp_col_name, part_names, **cg_config)
|
||||
|
||||
# 3 prepare lowpoly
|
||||
lowpoly_obj_names = parts_to_shells(part_names, lcs_pipeline, **cg_config)
|
||||
# Save midpoly setup as blender scene
|
||||
if kwargs['pipeline_type'] == 'midpoly':
|
||||
blend_file = os.path.join(
|
||||
blend_path,
|
||||
'{}_{}.blend'.format(assembly_name, kwargs['pipeline_type'])
|
||||
).replace('\\', '/')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
|
||||
logger.info('%s midpoly objects ready!', len(midpoly_obj_names))
|
||||
return blend_file
|
||||
|
||||
# 4) prepare lowpoly
|
||||
# ______________________________
|
||||
lowpoly_obj_names = parts_to_shells(part_names, **cg_config)
|
||||
uv_unwrap(lowpoly_obj_names)
|
||||
|
||||
# Save before baking
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
|
||||
# Save lowpoly setup as blender scene
|
||||
if kwargs['pipeline_type'] == 'lowpoly':
|
||||
blend_file = os.path.join(
|
||||
blend_path,
|
||||
'{}_{}.blend'.format(assembly_name, kwargs['pipeline_type'])
|
||||
).replace('\\', '/')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
|
||||
logger.info('%s lowpoly objects ready!', len(lowpoly_obj_names))
|
||||
return blend_file
|
||||
|
||||
# 4 bake textures
|
||||
if kwargs['textures_resolution'] != 0:
|
||||
# 5) bake textures
|
||||
# ______________________________
|
||||
if kwargs['textures_resolution'] == 0:
|
||||
logger.info('Baking pipeline has been canceled!')
|
||||
else:
|
||||
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
|
||||
blend_file = os.path.join(
|
||||
blend_path,
|
||||
'{}_{}.blend'.format(assembly_name, 'baking')
|
||||
).replace('\\', '/')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
|
||||
# 5 prepare textures
|
||||
bpy.ops.wm.quit_blender()
|
||||
|
@ -148,64 +220,45 @@ def cg_pipeline(**kwargs):
|
|||
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
|
||||
|
||||
# Save baking setup as blender scene
|
||||
if kwargs['pipeline_type'] == 'baking':
|
||||
blend_file = os.path.join(
|
||||
blend_path,
|
||||
'{}_{}.blend'.format(assembly_name, kwargs['pipeline_type'])
|
||||
).replace('\\', '/')
|
||||
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')
|
||||
logger.info('%s lowpoly objects baked!', len(lowpoly_obj_names))
|
||||
return blend_file
|
||||
|
||||
# 6 export object meshes
|
||||
# ______________________________
|
||||
# TODO asset manager
|
||||
# Save blender scene
|
||||
if kwargs['pipeline_type'] == 'export':
|
||||
blend_file = os.path.join(
|
||||
blend_path,
|
||||
'{}_{}.blend'.format(assembly_name, kwargs['pipeline_type'])
|
||||
).replace('\\', '/')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_file)
|
||||
|
||||
# 6 export object meshes and urdf
|
||||
to_urdf = collections.defaultdict(list)
|
||||
link = {}
|
||||
for part_name in part_names:
|
||||
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(
|
||||
export_fbx(
|
||||
obj_name=f'{part_name}_{cg_config["lowpoly"]}',
|
||||
path=export_path,
|
||||
subdir='collision')
|
||||
link[part_name] = link_prop
|
||||
to_urdf['links'].append(link)
|
||||
path=os.path.join(export_path, part_name, 'meshes').replace('\\', '/'))
|
||||
export_ply(
|
||||
obj_name=f'{part_name}_{cg_config["midpoly"]}',
|
||||
path=os.path.join(export_path, part_name, 'meshes').replace('\\', '/'))
|
||||
export_dae(
|
||||
obj_name=f'{part_name}_{cg_config["midpoly"]}',
|
||||
path=os.path.join(export_path, part_name, 'meshes').replace('\\', '/'))
|
||||
export_stl(
|
||||
obj_name=f'{part_name}_{cg_config["lowpoly"]}',
|
||||
path=os.path.join(export_path, part_name, 'meshes').replace('\\', '/'))
|
||||
|
||||
# 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 = {}
|
||||
# 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]:
|
||||
joint_prop['type'] = 'fixed'
|
||||
location = list(bpy.data.objects.get(pair[1]).location)
|
||||
rotation = list(bpy.data.objects.get(pair[0]).rotation_euler)
|
||||
# origin
|
||||
# round location values
|
||||
for idx, axis in enumerate(location):
|
||||
location[idx] = round(axis, 5)
|
||||
joint_prop['location'] = location
|
||||
joint_prop['rotation'] = rotation
|
||||
# parent
|
||||
joint_prop['parent'] = pair[0]
|
||||
# child
|
||||
joint_prop['child'] = pair[1]
|
||||
logger.info('%s parts exported!', len(part_names))
|
||||
return blend_file
|
||||
|
||||
joint['_'.join(pair)] = joint_prop
|
||||
|
||||
to_urdf['sequence'].append(joint)
|
||||
|
||||
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
|
||||
|
@ -261,14 +314,23 @@ if __name__ == '__main__':
|
|||
default='Robossembler_NonSolid',
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--pipeline_type',
|
||||
type=str,
|
||||
help='Set pipeline type: "cad", "highpoly", "midpoly", "lowpoly", "baking", "export"',
|
||||
default='export',
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--parts_sequence_path',
|
||||
type=str, help='Path to parts assembling sequence json file.',
|
||||
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.',
|
||||
type=str,
|
||||
help='Path for export assets. If not, fcstd_path will be used instead.',
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
|
@ -290,5 +352,3 @@ if __name__ == '__main__':
|
|||
cg_kwargs = {key: getattr(args, key) for key in dir(args) if not key.startswith('_')}
|
||||
|
||||
cg_pipeline(**cg_kwargs)
|
||||
|
||||
logger.info('CG Pipeline Completed!')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue