3

I got a model having a lot of other attributes like modifiers, material, vertex group, shape keys etc... already loaded onto it. Basically, I want to start with a fresh basic copy of the model's shape only (mesh data only) and leave out any additional attributes that is already included on the original model.

So far, doing Shift+D and S , only create a copy with all the attributes still present in. I just want the shape mesh only (vertices). Furthermore, I wonder if there any specific addon for this?

1 Answers1

2

As far as I know there's no way to duplicate an object copying only its mesh data using built-in commands. There are probably add-ons that do this as one of their functions, but I couldn't find one so I wrote this:

import bpy
from bpy.types import Operator

bl_info = { "name" : "Simple Duplicator", "description" : "Duplicate an object, copying only the mesh", "author" : "Marty Fouts <fouts@fogey.com>", "version" : (0, 0, 1), "blender" : (2, 83, 0), "location" : "View3D", "warning" : "", "support" : "COMMUNITY", "doc_url" : "", "category" : "Object" }

def object_mesh_duplicate_draw(self, context): """Menu entry for mesh Duplicator""" self.layout.separator() self.layout.operator("mop.mesh_duplicate", icon="OUTLINER_DATA_MESH")

This function was provided by Blender StackExchange User scurest

I've modified it to work in the context of the existing code.

def do_copy_pure_geometry(src_mesh): dst_mesh = bpy.data.meshes.new(name=src_mesh.name)

def copy_over(attr_name, sub_name, components=1):
    src_attr = getattr(src_mesh, attr_name)
    dst_attr = getattr(dst_mesh, attr_name)

    if len(dst_attr) != len(src_attr):
        dst_attr.add(len(src_attr))

    arr = [None] * (len(dst_attr) * components)
    src_attr.foreach_get(sub_name, arr)
    dst_attr.foreach_set(sub_name, arr)

copy_over(&quot;vertices&quot;, &quot;co&quot;, components=3)
copy_over(&quot;edges&quot;, &quot;vertices&quot;, components=2)
copy_over(&quot;loops&quot;, &quot;vertex_index&quot;)
copy_over(&quot;polygons&quot;, &quot;loop_start&quot;)
copy_over(&quot;polygons&quot;, &quot;loop_total&quot;)

dst_mesh.validate()
dst_mesh.update(calc_edges_loose=True)

return dst_mesh


class MOP_OT_mesh_duplicate(Operator): """Copy only the mesh of an object when duplicating it""" bl_idname = "mop.mesh_duplicate" bl_label = "duplicate mesh" bl_description = "Duplicate only the mesh of an object" bl_options = {'REGISTER', 'UNDO'}

@classmethod
def poll(cls, context):
    return context.mode == 'OBJECT' \
        and context.object \
        and context.object.type == 'MESH'

def execute(self, context):
    object = context.object

    new_mesh = do_copy_pure_geometry(object.data)
    new_object = bpy.data.objects.new(object.name, new_mesh)
    context.collection.objects.link(new_object)

    new_object.select_set(True)
    object.select_set(False)
    context.view_layer.objects.active = new_object
    return {'FINISHED'}

def register(): bpy.utils.register_class(MOP_OT_mesh_duplicate) bpy.types.VIEW3D_MT_object.append(object_mesh_duplicate_draw)

def unregister(): bpy.utils.unregister_class(MOP_OT_mesh_duplicate) bpy.types.VIEW3D_MT_object.remove(object_mesh_duplicate_draw)

if name == "main": register()

You can copy it into the text editor in Blender and run it, or you can install it as an add-on and enable it. Either way it adds an entry to the bottom of the 3D Viewport's Object menu called duplicate mesh that will duplicate the active object but only its mesh into a new object. You can also invoke it by using F3 and search for 'duplicate'. I didn't add a shortcut for it, because whatever I pick will probably be something you're already using.

Here's a test file with a non-mesh object and a mesh-object that has various additions to the mesh, including modifiers, marked edges, and vertex colors:

Marty Fouts
  • 33,070
  • 10
  • 35
  • 79
  • 1
    Your script copies all shape keys, materials, custom split normals, uv maps, vertex colors etc. as well... the question is how to copy the mesh only. – brockmann Nov 06 '21 at 17:16
  • @brockmann I fixed the copy. Thanks for correcting the place I missed removing bpy when I converted to an operator. Anything else? – Marty Fouts Nov 06 '21 at 17:53
  • 1
    It still keeps uv maps, vertex colors, seams, etc. if I am correct? – lemon Nov 06 '21 at 18:09
  • Cool. However, Lemon is correct, even the split normals are still there (might be an issue here). Also I personally would expect that the copy is selected and the active object after the duplication, see: https://blender.stackexchange.com/questions/126577/blender-2-8-api-python-set-active-object/126581#126581 – brockmann Nov 06 '21 at 18:22
  • If I can add. If the goal is to keep a "pure" mesh, I don't see another way than from_pydata. And, I suggest to use the "poll" function to avoid running it if it is not a mesh. Also, as the operator is in the object menu, no need to expect an edit mesh, I think... but I'm nitpicking – lemon Nov 06 '21 at 18:30
  • 1
    The easiest way to do a "pure" mesh copy is to foreach_get from the src mesh and foreach_set to the dst mesh. You only need to do vertices.co, edges.vertices, loops.vertex_index, polygons.loop_start, and polygons.loop_total. Example – scurest Nov 06 '21 at 19:00
  • @lemon thanks for the feedback and no I don't think you're nitpicking. The operator is in the object menu, but it's available in edit mode through the F3 menu, so I can't rely on being in object mode unless I take your suggestion and poll. Will do that. – Marty Fouts Nov 06 '21 at 19:19
  • @brockmann good point about duplicate usually making the resulting object active. I'll fix that – Marty Fouts Nov 06 '21 at 19:21
  • @scurest that somehow still copies seams. – Marty Fouts Nov 06 '21 at 19:37
  • I just tested and it doesn't. I don't see how it possibly could. – scurest Nov 06 '21 at 19:50
  • 1
    @scurest Found my problem. Going to post a modified script that complete works now. Thanks. – Marty Fouts Nov 06 '21 at 20:12
  • 1
    Can confirm, it works now and it's just the mesh. However, you did forget about the imports for bpy and the operator type, running your current script won't work out of the box. I had some time to polish it (deselecting the original, operator namespace, icon, menu separator etc.) and incorporated Lemons nitpicking re poll: https://pasteall.org/32Ty/raw – brockmann Nov 06 '21 at 20:58
  • @brockmann thanks for the polish. I believe I've got it all into this version, except see question. I added one more bit to the poll test to avoid the situation where there is no active object causing a fault. Also, a question: Why move the menu function? I didn't make that change simply because I didn't understand the reason. – Marty Fouts Nov 06 '21 at 22:19
  • Great. Doesn't really matter in this case... In larger scripts it's mostly for convenience to have those lines close to prepend/append call in order to be able to change things quickly and test where to put your UI, avoids scrolling and you can even see this in the templates e.g. Import/Export operator. Also we are reading from top to bottom and I personally think the most important/relevant parts (operators) should be on top of the script to get the logic quickly as possible. Anyway, do whatever you like, there is no rule for 5 lines of code. – brockmann Nov 06 '21 at 23:40
  • makes sense. I use VS Code and collapse things a lot, so I'm used to that no mattering. Also, I've added the idea of class initialize/deinitialize functions that get called from register/unregister if the exist so that I can keep things like keymaps and menu entries local to the operator class file. So we're thinking the same way about organization, but I'm using VS Code as an aid. – Marty Fouts Nov 06 '21 at 23:56