diff --git a/asp/mocks/urdf/asm.urdf b/asp/mocks/urdf/asm.urdf
new file mode 100644
index 0000000..b8d901a
--- /dev/null
+++ b/asp/mocks/urdf/asm.urdf
@@ -0,0 +1,4 @@
+
+
+
diff --git a/asp/mocks/urdf/joint.urdf b/asp/mocks/urdf/joint.urdf
new file mode 100644
index 0000000..611aa12
--- /dev/null
+++ b/asp/mocks/urdf/joint.urdf
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/asp/mocks/urdf/link.urdf b/asp/mocks/urdf/link.urdf
new file mode 100644
index 0000000..b20131f
--- /dev/null
+++ b/asp/mocks/urdf/link.urdf
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cg/blender/export/__init__.py b/cg/blender/export/__init__.py
index a3bda60..c5c9e01 100644
--- a/cg/blender/export/__init__.py
+++ b/cg/blender/export/__init__.py
@@ -1,7 +1,52 @@
# -*- coding: utf-8 -*-
-"""
+# Copyright (C) 2023 Ilia Kurochkin
+#
+# 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
diff --git a/cg/blender/export/dae.py b/cg/blender/export/dae.py
index b85a2b0..42d2a95 100644
--- a/cg/blender/export/dae.py
+++ b/cg/blender/export/dae.py
@@ -1,34 +1,64 @@
# -*- coding: utf-8 -*-
-"""
+# Copyright (C) 2023 Ilia Kurochkin
+#
+# 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
diff --git a/cg/blender/export/stl.py b/cg/blender/export/stl.py
index fd5d5cd..9b3d916 100644
--- a/cg/blender/export/stl.py
+++ b/cg/blender/export/stl.py
@@ -1,34 +1,41 @@
# -*- coding: utf-8 -*-
-"""
+# Copyright (C) 2023 Ilia Kurochkin
+#
+# 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
diff --git a/cg/blender/import_cad/build_blender_scene.py b/cg/blender/import_cad/build_blender_scene.py
index db060db..f8ee0cc 100644
--- a/cg/blender/import_cad/build_blender_scene.py
+++ b/cg/blender/import_cad/build_blender_scene.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-# Original code by (C) 2019 yorikvanhavre
# Copyright (C) 2023 Ilia Kurochkin
#
# 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
diff --git a/cg/blender/processing/highpoly_setup.py b/cg/blender/processing/highpoly_setup.py
index 0f3361c..ed9dc82 100644
--- a/cg/blender/processing/highpoly_setup.py
+++ b/cg/blender/processing/highpoly_setup.py
@@ -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))
diff --git a/cg/blender/processing/lowpoly_setup.py b/cg/blender/processing/lowpoly_setup.py
index e75e4b8..2e7ae83 100644
--- a/cg/blender/processing/lowpoly_setup.py
+++ b/cg/blender/processing/lowpoly_setup.py
@@ -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]
diff --git a/cg/blender/processing/restruct_hierarchy_by_lcs.py b/cg/blender/processing/restruct_hierarchy_by_lcs.py
index 1074668..88742d4 100644
--- a/cg/blender/processing/restruct_hierarchy_by_lcs.py
+++ b/cg/blender/processing/restruct_hierarchy_by_lcs.py
@@ -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!')
diff --git a/cg/blender/processing/uv_setup.py b/cg/blender/processing/uv_setup.py
index 40989a9..ccb720d 100644
--- a/cg/blender/processing/uv_setup.py
+++ b/cg/blender/processing/uv_setup.py
@@ -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))
+
diff --git a/cg/pipeline/cg_pipeline.py b/cg/pipeline/cg_pipeline.py
index 3a50296..942a117 100644
--- a/cg/pipeline/cg_pipeline.py
+++ b/cg/pipeline/cg_pipeline.py
@@ -6,9 +6,11 @@ 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
@@ -18,10 +20,10 @@ from blender.processing.restruct_hierarchy_by_lcs import restruct_hierarchy
from blender.processing.highpoly_setup import setup_meshes
from blender.processing.lowpoly_setup import parts_to_shells
from blender.processing.uv_setup import uv_unwrap
+from blender.export.dae import export_dae
+from blender.export.stl import export_stl
import bpy
import mathutils
-# from export.dae import export_dae
-# from export.collision import export_col_stl
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
@@ -64,12 +66,15 @@ 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)
+
# prepare blend file
remove_collections()
cleanup_orphan_data()
# convert FreeCAD scene to Blender scene
- objs_for_render = json_to_blend(
+ imported_objects = json_to_blend(
json.loads(
cmd_proc(freecadcmd,
fcstd_data_script,
@@ -80,36 +85,84 @@ def cg_pipeline(**kwargs):
)
# restructuring hierarchy by lcs points
- lcs_objects = restruct_hierarchy()
+ if imported_objects['objs_lcs']:
+ restruct_hierarchy(imported_objects['objs_lcs'])
# save blender scene
- if kwargs['blend_path'] is not None:
- if not os.path.isdir(os.path.dirname(kwargs['blend_path'])):
- os.makedirs(os.path.dirname(kwargs['blend_path']))
- bpy.ops.wm.save_as_mainfile(filepath=kwargs['blend_path'])
+ 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)
# prepare highpoly
- setup_meshes(objs_for_render, sharpness=True, shading=True)
+ 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 lowpoly
- part_names = [p.name.split(inlet)[0] for p in lcs_objects if p.name.endswith(inlet)]
lowpoly_objs = parts_to_shells(part_names)
uv_unwrap(lowpoly_objs)
# save blender scene
- if kwargs['blend_path'] is not None:
- if not os.path.isdir(os.path.dirname(kwargs['blend_path'])):
- os.makedirs(os.path.dirname(kwargs['blend_path']))
- bpy.ops.wm.save_as_mainfile(filepath=kwargs['blend_path'])
+ 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)
- # export all objects
- if kwargs['mesh_export_path'] is not None:
- obs = bpy.context.selected_objects
- for ob in obs:
- ob.matrix_world = mathutils.Matrix()
- for ob in obs:
- ob.select_set(state=True)
- export_dae(kwargs['mesh_export_path'])
- export_col_stl(kwargs['mesh_export_path'])
+ # export object meshes and urdf
+ to_urdf = collections.defaultdict(list)
+
+ if lowpoly_objs:
+ export_objs = lowpoly_objs
+ else:
+ export_objs = sum([imported_objects['objs_foreground'],
+ imported_objects['objs_background']], [])
+
+ link = {}
+ for export_obj in export_objs:
+ link_prop = {}
+ if mesh_export_path is not None:
+ link_prop['visual'] = export_dae(
+ obj_name=export_obj, path=mesh_export_path, subdir='visual')
+ link_prop['collision'] = export_stl(
+ obj_name=export_obj, path=mesh_export_path, subdir='collision')
+
+ link[export_obj] = 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 = {}
+ 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__':