Advertisement
Guest User

Blender 3.x merge objects (non-destructive)

a guest
Jan 23rd, 2022
136
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.02 KB | None | 0 0
  1. import bpy, bmesh
  2.  
  3. def merge_objects(objects, name=None, obj_matrix=None):
  4.     '''
  5.    Non-destructive merge - Objects will be merged IN PLACE wherever they are in the world.
  6.    setting obj_matrix simply defines the new object's origin matrix,
  7.    otherwise the first object in the list will be used as the new object's origin (visual
  8.    geometry will not be affected).
  9.    '''
  10.    
  11.     if name is None:
  12.         name = "JoinedMesh"
  13.    
  14.     # create a temp mesh to copy everything into
  15.     if name in bpy.data.meshes:
  16.         bpy.data.meshes.remove(bpy.data.meshes[name])
  17.  
  18.     final_mesh = bpy.data.meshes.new(name)
  19.  
  20.     def add_data_to_mesh(o, me, extra_transform=None):
  21.         '''
  22.        Creates a bmesh for a given mesh, then adds it to the existing output mesh data.
  23.        extra_transform is used for instances and nested instances.
  24.        '''
  25.  
  26.         # 'bake' visual normals to custom split normals, regardless of what type of smoothing is being used. This ensures our final
  27.         # mesh is visually consistent with the source objects- if something looked faceted and sharp, it should look faceted and sharp
  28.         # in the merged object even if it was merged into something with weighted normals, or something that was had autosmoothing set
  29.         # to 180 degrees.
  30.         me.calc_normals_split()
  31.         me.normals_split_custom_set([l.normal for l in me.loops])
  32.  
  33.         # add any new materials to the temp mesh and create a re-indexing map
  34.         mat_index_lookup = {}
  35.         if not me.materials:
  36.             # adding a null material here to avoid a situation where a mesh that does not use a material gets automatically assigned the
  37.             # first mat that was found (which is what happens if you use the built-in join operator)
  38.             me.materials.append(None)
  39.         for i, m in enumerate(me.materials):
  40.             if m not in final_mesh.materials.values():
  41.                 mat_index_lookup[i] = len(final_mesh.materials)
  42.                 final_mesh.materials.append(m)
  43.             else:
  44.                 new_idx = final_mesh.materials.values().index(m)
  45.                 if i != new_idx:                    
  46.                     mat_index_lookup[i] = final_mesh.materials.values().index(m)
  47.  
  48.         bm = bmesh.new()
  49.         bm.from_mesh(me)
  50.  
  51.         mat_face_reindex = [f for f in bm.faces if f.material_index in mat_index_lookup]
  52.         for f in mat_face_reindex:
  53.             f.material_index = mat_index_lookup[f.material_index]
  54.  
  55.         # transform the bmesh to match the object transform
  56.         if extra_transform is not None:
  57.             bmesh.ops.transform(bm, matrix=extra_transform @ o.evaluated_get(bpy.context.view_layer.depsgraph).matrix_world, verts=bm.verts)
  58.         else:
  59.             bmesh.ops.transform(bm, matrix=o.evaluated_get(bpy.context.view_layer.depsgraph).matrix_world, verts=bm.verts)
  60.  
  61.         # copy in the existing mesh data before writing everything back out
  62.         bm.from_mesh(final_mesh)
  63.         bm.to_mesh(final_mesh)
  64.         return bm
  65.  
  66.     def add_mesh_object(co):
  67.         return add_data_to_mesh(co.evaluated_get(bpy.context.view_layer.depsgraph), co.evaluated_get(bpy.context.view_layer.depsgraph).data, extra_transform=this_matrix_offset)
  68.        
  69.     def handle_object_recursive(o, extra_transform=None):
  70.         bm = None
  71.         if o.type == 'EMPTY' and o.instance_collection is not None:
  72.             # loop through each object in the collection and add the data to the mesh
  73.             for co in o.instance_collection.all_objects:
  74.                 this_matrix_offset = None
  75.                 if extra_transform is not None:
  76.                     # if extra_transform isn't None, o is already a nested instance. need to offset each object in the source
  77.                     # collection by the incoming extra_transform AND the local offset of the instance empty itself. since
  78.                     # this function is recursive, any additional nested instances will inherit this transformation and will
  79.                     # continuously update the matrix offset to match the visual transform of that particular geometry.
  80.                     this_matrix_offset = extra_transform @ o.matrix_world
  81.                 else:
  82.                     # if extra_transform is None, it means we're working with a first-level object inside an instance.
  83.                     # We only need to offset the overall transformation by the instance empty, the contents of the instance will
  84.                     # be handled recursively, which is all kicked off by setting the initial offset here.
  85.                     this_matrix_offset = o.matrix_world
  86.                 if co.type == 'MESH':
  87.                     bm = add_data_to_mesh(co.evaluated_get(bpy.context.view_layer.depsgraph), co.evaluated_get(bpy.context.view_layer.depsgraph).data, extra_transform=this_matrix_offset)
  88.                 elif co.type == 'EMPTY' and co.instance_collection is not None:
  89.                     bm = handle_object_recursive(co, extra_transform=this_matrix_offset)
  90.                 elif co.type == 'CURVE':
  91.                     bm = add_data_to_mesh(co, co.evaluated_get(bpy.context.view_layer.depsgraph).to_mesh(), extra_transform=this_matrix_offset)
  92.                     o.evaluated_get(bpy.context.view_layer.depsgraph).to_mesh_clear()
  93.         elif o.type == 'MESH':
  94.             bm = add_data_to_mesh(o, o.evaluated_get(bpy.context.view_layer.depsgraph).data)
  95.         elif o.type == 'CURVE':
  96.             bm = add_data_to_mesh(o, o.evaluated_get(bpy.context.view_layer.depsgraph).to_mesh())
  97.             o.evaluated_get(bpy.context.view_layer.depsgraph).to_mesh_clear()
  98.         else:
  99.             # TODO: handle other mesh-like types, such as metaballs.
  100.             print(f"Skipping {o.name}, unsupported type ({o.type})")
  101.         return bm
  102.  
  103.  
  104.  
  105.     # kick off the recursive bmesh builder
  106.     bm = None
  107.     for i, o in enumerate(objects):
  108.         bm = handle_object_recursive(o)
  109.  
  110.     if bm is None:
  111.         bpy.data.meshes.remove(final_mesh)
  112.         return None
  113.  
  114.     # need autosmoothing turned on to see custom split normals
  115.     final_mesh.use_auto_smooth = True
  116.  
  117.     # create the object itself and handle the matrix offset to get the origin into the requested location.
  118.     ob = bpy.data.objects.new(name, final_mesh)
  119.     bpy.context.scene.collection.objects.link(ob)
  120.    
  121.     if obj_matrix is not None:
  122.         ob.matrix_world = obj_matrix
  123.         bmesh.ops.transform(bm, matrix=obj_matrix.inverted(), verts=bm.verts)
  124.     else:
  125.         ob.matrix_world = objects[0].matrix_world
  126.         bmesh.ops.transform(bm, matrix=objects[0].matrix_world.inverted(), verts=bm.verts)
  127.  
  128.     # done
  129.     bm.to_mesh(final_mesh)
  130.     bm.free()    
  131.     bpy.context.view_layer.update()
  132.     return ob
  133.  
  134. context = bpy.context
  135. selected_objects = context.selected_objects
  136. result = merge_objects(selected_objects, name="MergedResult", obj_matrix=context.active_object.matrix_world if context.active_object else None)
  137.  
  138. if result:
  139.     bpy.ops.object.select_all(action="DESELECT")
  140.     result.select_set(True)
  141.     context.view_layer.objects.active = result
  142.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement