259 lines
7.8 KiB
Python
259 lines
7.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
#!/usr/bin/env python
|
|
'''
|
|
DESCRIPTION.
|
|
Convert and setup FreeCAD solid objects to 3d assets.
|
|
Support Blender compiled as a Python Module only!
|
|
'''
|
|
__version__ = '0.6'
|
|
import collections
|
|
import json
|
|
import logging
|
|
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 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.texturing.bake_submitter import bw_submit
|
|
from blender.texturing.composing import compose_baked_textures
|
|
from blender.export.dae import export_dae
|
|
from blender.export.stl import export_stl
|
|
import bpy
|
|
import mathutils
|
|
|
|
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
|
|
|
|
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'
|
|
|
|
|
|
def cg_pipeline(**kwargs):
|
|
''' CG asset creation pipeline '''
|
|
|
|
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()
|
|
cleanup_orphan_data()
|
|
|
|
# convert FreeCAD scene to Blender scene
|
|
imported_objects = json_to_blend(
|
|
json.loads(
|
|
cmd_proc(freecadcmd,
|
|
fcstd_data_script,
|
|
'--',
|
|
**kwargs
|
|
).split('FreeCAD ')[0]
|
|
)
|
|
)
|
|
|
|
# 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)
|
|
|
|
# restructuring hierarchy by lcs points
|
|
if imported_objects['objs_lcs']:
|
|
restruct_hierarchy(imported_objects['objs_lcs'])
|
|
|
|
# prepare highpoly
|
|
if imported_objects['objs_foreground']:
|
|
setup_meshes(imported_objects['objs_foreground'], sharpness=True, shading=True)
|
|
else:
|
|
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
|
|
)
|
|
hightpoly_collections_to_midpoly(part_names)
|
|
|
|
# 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))
|
|
bpy.ops.wm.save_as_mainfile(filepath=blend_path)
|
|
|
|
# bake textures
|
|
bpy.ops.wm.open_mainfile(filepath=blend_path)
|
|
textures_path = bw_submit(lowpoly_obj_names)
|
|
compose_baked_textures(textures_path)
|
|
|
|
# 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:
|
|
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
|
|
|
|
to_urdf['links'].append(link)
|
|
|
|
#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]
|
|
|
|
joint['_'.join(pair)] = joint_prop
|
|
|
|
to_urdf['sequence'].append(joint)
|
|
|
|
print(json.dumps(to_urdf, indent=4))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description='Convert and setup FreeCAD solid objects to 3d assets mesh files.'
|
|
)
|
|
parser.add_argument(
|
|
'--fcstd_path',
|
|
type=str,
|
|
help='Path to source FreeCAD scene',
|
|
required=True
|
|
)
|
|
parser.add_argument(
|
|
'--tesselation_method',
|
|
type=str,
|
|
help='Select tesselation method: Standard or FEM.',
|
|
default='Standard',
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--linear_deflection',
|
|
type=float,
|
|
help='Max linear distance error',
|
|
default=0.1,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--angular_deflection',
|
|
type=float,
|
|
help='Max angular distance error',
|
|
default=20.0,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--fem_size',
|
|
type=float,
|
|
help='For FEM method only! Finite element size in mm',
|
|
default=50.0,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--skiphidden',
|
|
type=bool,
|
|
help='Skip processing for hidden FreeCAD objects',
|
|
default=True,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--property_forse_nonsolid',
|
|
type=str,
|
|
help='FreeCAD property to enable processing for nonsolid objects',
|
|
default='Robossembler_NonSolid',
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--mesh_export_path',
|
|
type=str, help='Path for export meshes',
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
'--blend_path',
|
|
type=str,
|
|
help='Path for export blend assembly file',
|
|
required=False
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
kwargs = {key: getattr(args, key) for key in dir(args) if not key.startswith('_')}
|
|
|
|
cg_pipeline(**kwargs)
|
|
|
|
logger.info('CG Pipeline Completed!')
|