Tool for convert FreeCAD sub-assemblies to URDF-world according to config
This commit is contained in:
parent
03bc34539c
commit
184ac7df88
12 changed files with 327 additions and 119 deletions
|
@ -1,7 +1,52 @@
|
|||
# -*- 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.
|
||||
Blender export modules.
|
||||
Modules exports all objests in scene.
|
||||
You can set export path and subdir.
|
||||
"""
|
||||
Decorator for export functions.
|
||||
'''
|
||||
import logging
|
||||
import os
|
||||
import bpy
|
||||
import mathutils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def export_decorator(func):
|
||||
|
||||
def wrapper(**kwargs):
|
||||
# add defaults
|
||||
kwargs.setdefault('path', '//')
|
||||
kwargs.setdefault('subdir', '')
|
||||
|
||||
obj = bpy.data.objects.get(kwargs['obj_name'])
|
||||
# deselect all but just one object and make it active
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
# clean hierarchy and transforms
|
||||
obj.parent = None
|
||||
obj.matrix_world = mathutils.Matrix()
|
||||
# construct path
|
||||
filename = bpy.context.active_object.name
|
||||
filepath = os.path.join(kwargs['path'],
|
||||
kwargs['subdir']).replace('\\', '/')
|
||||
if not os.path.isdir(filepath):
|
||||
os.makedirs(filepath)
|
||||
# store path
|
||||
kwargs['outpath'] = os.path.join(filepath, filename)
|
||||
# return export function
|
||||
return func(**kwargs)
|
||||
|
||||
return wrapper
|
||||
|
|
|
@ -1,34 +1,64 @@
|
|||
# -*- 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.
|
||||
Collada mesh exporter.
|
||||
Exports all objects in scene.
|
||||
You can set export path and subdir.
|
||||
"""
|
||||
__version__ = "0.1"
|
||||
'''
|
||||
__version__ = "0.2"
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import bpy
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
from blender.export import export_decorator
|
||||
|
||||
|
||||
def export_dae(path, subdir=""):
|
||||
""" Collada mesh exporter. Exports all objects in scene. """
|
||||
for ob in bpy.context.scene.objects:
|
||||
# deselect all but just one object and make it active
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
ob.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = ob
|
||||
filename = bpy.context.active_object.name
|
||||
# export dae
|
||||
dae_path = os.path.join(path, subdir).replace('\\', '/')
|
||||
if not os.path.isdir(dae_path):
|
||||
os.makedirs(dae_path)
|
||||
outpath = os.path.join(dae_path, filename)
|
||||
logger.debug('vizual:', outpath)
|
||||
@export_decorator
|
||||
def export_dae(**kwargs):
|
||||
outpath = ('{}.dae'.format(kwargs['outpath']))
|
||||
|
||||
bpy.ops.wm.collada_export(filepath=outpath, check_existing=False, apply_modifiers=True, export_mesh_type=0, export_mesh_type_selection='view', export_global_forward_selection='Y', export_global_up_selection='Z', apply_global_orientation=False, selected=True, include_children=False, include_armatures=False, include_shapekeys=False, deform_bones_only=False, include_animations=False, include_all_actions=True, export_animation_type_selection='sample', sampling_rate=1, keep_smooth_curves=False, keep_keyframes=False, keep_flat_curves=False, active_uv_only=False, use_texture_copies=True, triangulate=True, use_object_instantiation=True, use_blender_profile=True, sort_by_name=False, export_object_transformation_type=0, export_object_transformation_type_selection='matrix', export_animation_transformation_type=0, export_animation_transformation_type_selection='matrix', open_sim=False, limit_precision=False, keep_bind_info=False)
|
||||
bpy.ops.wm.collada_export(
|
||||
filepath=outpath,
|
||||
check_existing=False,
|
||||
apply_modifiers=True,
|
||||
export_mesh_type=0,
|
||||
export_mesh_type_selection='view',
|
||||
export_global_forward_selection='Y',
|
||||
export_global_up_selection='Z',
|
||||
apply_global_orientation=False,
|
||||
selected=True,
|
||||
include_children=False,
|
||||
include_armatures=False,
|
||||
include_shapekeys=False,
|
||||
deform_bones_only=False,
|
||||
include_animations=False,
|
||||
include_all_actions=True,
|
||||
export_animation_type_selection='sample',
|
||||
sampling_rate=1,
|
||||
keep_smooth_curves=False,
|
||||
keep_keyframes=False,
|
||||
keep_flat_curves=False,
|
||||
active_uv_only=False,
|
||||
use_texture_copies=True,
|
||||
triangulate=True,
|
||||
use_object_instantiation=True,
|
||||
use_blender_profile=True,
|
||||
sort_by_name=False,
|
||||
export_object_transformation_type=0,
|
||||
export_object_transformation_type_selection='matrix',
|
||||
export_animation_transformation_type=0,
|
||||
export_animation_transformation_type_selection='matrix',
|
||||
open_sim=False,
|
||||
limit_precision=False,
|
||||
keep_bind_info=False)
|
||||
|
||||
return outpath
|
||||
|
|
|
@ -1,34 +1,41 @@
|
|||
# -*- 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.
|
||||
STL mesh exporter.
|
||||
Exports all objects in scene.
|
||||
You can set export path and subdir.
|
||||
"""
|
||||
__version__ = "0.1"
|
||||
'''
|
||||
__version__ = "0.2"
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import bpy
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
from blender.export import export_decorator
|
||||
|
||||
|
||||
def export_stl(path, subdir=""):
|
||||
""" STL mesh exporter. Exports all objects in scene. """
|
||||
for ob in bpy.context.scene.objects:
|
||||
# deselect all but just one object and make it active
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
ob.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = ob
|
||||
filename = bpy.context.active_object.name
|
||||
# export stl
|
||||
stl_path = os.path.join(path, subdir).replace('\\', '/')
|
||||
if not os.path.isdir(stl_path):
|
||||
os.makedirs(stl_path)
|
||||
outpath = os.path.join(stl_path, filename+'.stl')
|
||||
logger.debug('collision:', outpath)
|
||||
@export_decorator
|
||||
def export_stl(**kwargs):
|
||||
outpath = ('{}.stl'.format(kwargs['outpath']))
|
||||
|
||||
bpy.ops.export_mesh.stl(filepath=outpath, check_existing=False, filter_glob='*.stl', use_selection=True, global_scale=1.0, use_scene_unit=False, ascii=False, use_mesh_modifiers=True, batch_mode='OFF', axis_forward='Y', axis_up='Z')
|
||||
bpy.ops.export_mesh.stl(filepath=outpath,
|
||||
check_existing=False,
|
||||
filter_glob='*.stl',
|
||||
use_selection=True,
|
||||
global_scale=1000,
|
||||
use_scene_unit=False,
|
||||
ascii=False,
|
||||
use_mesh_modifiers=True,
|
||||
batch_mode='OFF',
|
||||
axis_forward='Y',
|
||||
axis_up='Z')
|
||||
|
||||
return outpath
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Original code by (C) 2019 yorikvanhavre <yorik@uncreated.net>
|
||||
# Copyright (C) 2023 Ilia Kurochkin <brothermechanic@gmail.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
|
@ -21,6 +20,7 @@ DESCRIPTION.
|
|||
'''
|
||||
__version__ = '0.1'
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import random
|
||||
import bpy
|
||||
|
@ -66,8 +66,7 @@ def json_to_blend(js_data):
|
|||
|
||||
fc_file = list(js_data.keys())[0]
|
||||
|
||||
bobjs = []
|
||||
bobjs_for_render = []
|
||||
imported_objects = collections.defaultdict(list)
|
||||
|
||||
for js_obj in js_data[fc_file]:
|
||||
bobj = None
|
||||
|
@ -78,6 +77,7 @@ def json_to_blend(js_data):
|
|||
bobj.empty_display_size = round(random.uniform(0.05, 0.15), 3)
|
||||
bobj.show_in_front = True
|
||||
lcs_collection.objects.link(bobj)
|
||||
imported_objects['objs_lcs'].append(bobj.name)
|
||||
|
||||
elif js_data[fc_file][js_obj]['type'] == 'PART':
|
||||
if js_data[fc_file][js_obj].get('mesh'):
|
||||
|
@ -107,24 +107,26 @@ def json_to_blend(js_data):
|
|||
scene_scale)
|
||||
for hierarchy_obj in hierarchy_objs:
|
||||
hierarchy_collection.objects.link(hierarchy_obj)
|
||||
imported_objects['objs_hierarchy'].append(hierarchy_obj.name)
|
||||
|
||||
# one material for the whole object
|
||||
if bobj.type == 'MESH':
|
||||
if js_data[fc_file][js_obj].get('material'):
|
||||
fem_mat = js_data[fc_file][js_obj]['material']
|
||||
assign_materials(bobj, fem_mat)
|
||||
bobjs_for_render.append(bobj)
|
||||
imported_objects['objs_foreground'].append(bobj.name)
|
||||
else:
|
||||
assign_black(bobj)
|
||||
|
||||
bobjs.append(bobj)
|
||||
imported_objects['objs_background'].append(bobj.name)
|
||||
|
||||
# losted root lcs inlet workaround
|
||||
lcs_objects = lcs_collection.objects
|
||||
if lcs_objects:
|
||||
root_lcs = [lcs for lcs in lcs_objects if lcs.name.endswith(root)]
|
||||
if imported_objects['objs_lcs']:
|
||||
root_lcs = None
|
||||
for obj_name in imported_objects['objs_lcs']:
|
||||
if obj_name.endswith(root):
|
||||
root_lcs = bpy.data.objects[obj_name]
|
||||
break
|
||||
if root_lcs:
|
||||
root_lcs = root_lcs[0]
|
||||
root_inlet_name = '{}{}'.format(root_lcs.name.split(root)[0], inlet)
|
||||
if not bpy.data.objects.get(root_inlet_name):
|
||||
root_inlet = bpy.data.objects.new(root_inlet_name, None)
|
||||
|
@ -135,10 +137,17 @@ def json_to_blend(js_data):
|
|||
root_inlet.rotation_euler = root_lcs.rotation_euler
|
||||
root_inlet.parent = root_lcs.parent
|
||||
lcs_collection.objects.link(root_inlet)
|
||||
imported_objects['objs_lcs'].append(root_inlet.name)
|
||||
logger.info('Root Inlet LCS object created!')
|
||||
else:
|
||||
logger.info('Root Inlet LCS object already exists!')
|
||||
else:
|
||||
logger.info('Lost root LCS object!')
|
||||
logger.info('Lost Root LCS object!')
|
||||
else:
|
||||
logger.info('No LCS objects found!')
|
||||
|
||||
# TODO
|
||||
# update do not dork
|
||||
logger.info('Generated %s objects without errors', len(bobjs))
|
||||
return bobjs_for_render
|
||||
logger.info('Generated %s objects without errors',
|
||||
len(sum(list(imported_objects.values()), [])))
|
||||
return imported_objects
|
||||
|
|
|
@ -24,15 +24,16 @@ logger = logging.getLogger(__name__)
|
|||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def setup_meshes(bobjs, cleanup=False, sharpness=False, shading=False):
|
||||
def setup_meshes(obj_names, cleanup=False, sharpness=False, shading=False):
|
||||
''' Setup raw meshes list after importing '''
|
||||
logger.info('Hightpoly meshes setup launched...')
|
||||
for bobj in bobjs:
|
||||
if not bobj.type == 'MESH':
|
||||
for obj_name in obj_names:
|
||||
obj = bpy.data.objects[obj_name]
|
||||
if not obj.type == 'MESH':
|
||||
continue
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bobj.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = bobj
|
||||
obj.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
|
||||
if cleanup:
|
||||
# remove doubles
|
||||
|
@ -68,4 +69,4 @@ def setup_meshes(bobjs, cleanup=False, sharpness=False, shading=False):
|
|||
bpy.context.object.modifiers['triangulate'].keep_custom_normals = 1
|
||||
bpy.context.object.modifiers['triangulate'].show_expanded = 0
|
||||
|
||||
return logger.info('Hightpoly meshes setup finished!')
|
||||
return logger.info('Setup of %s hightpoly meshes is finished!', len(obj_names))
|
||||
|
|
|
@ -135,6 +135,6 @@ def parts_to_shells(hightpoly_part_names):
|
|||
bpy.ops.mesh.select_mode(type='FACE')
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
logger.info('Lowpoly shells created successfully!')
|
||||
logger.info('Generation of %s lowpoly shells is finished!', len(lowpoly_col.objects))
|
||||
|
||||
return lowpoly_col.objects
|
||||
return [obj.name for obj in lowpoly_col.objects]
|
||||
|
|
|
@ -131,28 +131,42 @@ def lcs_collections(root_lcs, lcs_objects):
|
|||
return root_lcs.children
|
||||
|
||||
|
||||
def restruct_hierarchy():
|
||||
def restruct_hierarchy(lcs_names):
|
||||
''' Execute restructurisation. '''
|
||||
|
||||
lcs_objects = bpy.data.collections[lcs_col_name].objects
|
||||
#lcs_objects = bpy.data.collections[lcs_col_name].objects
|
||||
lcs_objects = []
|
||||
root_lcs = None
|
||||
if lcs_names:
|
||||
for obj_name in lcs_names:
|
||||
if obj_name.endswith(root):
|
||||
root_lcs = bpy.data.objects[obj_name]
|
||||
lcs_objects.append(bpy.data.objects[obj_name])
|
||||
|
||||
main_locator = [obj for obj in bpy.data.objects if not obj.parent][0]
|
||||
root_lcs = [lcs for lcs in lcs_objects if lcs.name.endswith(root)][0]
|
||||
lcs_objects = [lcs for lcs in lcs_objects if lcs != root_lcs]
|
||||
root_locator = root_lcs.parent
|
||||
unparenting(root_lcs)
|
||||
round_transforms(root_lcs)
|
||||
unparenting(root_locator)
|
||||
parenting(root_lcs, root_locator)
|
||||
parenting(root_lcs, main_locator)
|
||||
main_locators = [obj for obj in bpy.data.objects if not obj.parent]
|
||||
if len(main_locators) > 1:
|
||||
logger.info('Scene has several main (root) locators! '
|
||||
'This may cause an error!')
|
||||
|
||||
retree_by_lcs(lcs_objects, root_lcs)
|
||||
lcs_constrainting(lcs_objects, root_lcs)
|
||||
if root_lcs:
|
||||
lcs_objects = [lcs for lcs in lcs_objects if lcs != root_lcs]
|
||||
root_locator = root_lcs.parent
|
||||
unparenting(root_lcs)
|
||||
round_transforms(root_lcs)
|
||||
unparenting(root_locator)
|
||||
parenting(root_lcs, root_locator)
|
||||
parenting(root_lcs, main_locators[0])
|
||||
|
||||
lcs_collections(root_lcs, lcs_objects)
|
||||
retree_by_lcs(lcs_objects, root_lcs)
|
||||
lcs_constrainting(lcs_objects, root_lcs)
|
||||
|
||||
# remove unused for now collection
|
||||
bpy.data.collections.remove(bpy.data.collections[hierarchy_col_name])
|
||||
lcs_collections(root_lcs, lcs_objects)
|
||||
|
||||
logger.info('Restructuring pipeline finished!')
|
||||
return lcs_objects
|
||||
# remove unused for now collection
|
||||
bpy.data.collections.remove(bpy.data.collections[hierarchy_col_name])
|
||||
|
||||
return logger.info('Restructuring pipeline finished!')
|
||||
else:
|
||||
return logger.info('Lost root LCS object!')
|
||||
else:
|
||||
return logger.info('Restructuring pipeline canceled!')
|
||||
|
|
|
@ -24,9 +24,10 @@ logger = logging.getLogger(__name__)
|
|||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def uv_unwrap(objs, angle_limit=30):
|
||||
def uv_unwrap(obj_names, angle_limit=30):
|
||||
''' UV unwrapping and UV packing processing '''
|
||||
for obj in objs:
|
||||
for obj_name in obj_names:
|
||||
obj = bpy.data.objects[obj_name]
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
@ -60,5 +61,5 @@ def uv_unwrap(objs, angle_limit=30):
|
|||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
obj.select_set(False)
|
||||
|
||||
logger.info('UV unwrapping and UV packing processing done successfully!')
|
||||
return objs
|
||||
return logger.info('UV setup of %s lowpoly meshes is finished!', len(obj_names))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue