# -*- 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.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) bw_submit(lowpoly_obj_names) # 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!')