diff --git a/New object from selected.py b/New object from selected.py new file mode 100644 index 0000000..dc7fb63 --- /dev/null +++ b/New object from selected.py @@ -0,0 +1,266 @@ +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# ***** END GPL LICENSE BLOCK ***** + +bl_info = { + "name": "New Object From Selected Geometry", + "description": "Creates a new object from the selected geometry in Edit mode", + "author": "Giambattista Caltabiano", + "version": (1, 0), + "blender": (3, 5, 0), + "location": "View3D > Mesh", + "warning": "", + "doc_url": "https://projects.blender.org/Giambattista-Caltabiano/NewObjectFromSelectedGeometryAdd-on.git", + "tracker_url": "https://projects.blender.org/Giambattista-Caltabiano/NewObjectFromSelectedGeometryAdd-on.git/issues", + "category": "Mesh", +} + +import bpy + +def isEven(num): + if num % 2 == 0: + return True + else: return False + + +class ObjectFromGeometry(bpy.types.Operator): + """Create an object from selected geometry""" # Use this as a tooltip for menu items and buttons. + bl_idname = "object.new_from_geometry" # Unique identifier for buttons and menu items to reference. + bl_label = "New object from selected geometry" # Display name in the interface. + bl_options = {'REGISTER', 'UNDO'} # Enable undo for the operator. + + #it doesn't work... could not find a way to disable properties/options + #def modeOptionsChanged(self, context): + # if self.modeOptions == "useVerts": # enable verts options + # self.drawEdges.options = {} + # self.evenOdd.options = {} + # self.joinEnds.options = {} + # else: # disable verts options + # self.drawEdges.options = {'HIDDEN'} + # self.evenOdd.options = {'HIDDEN'} + # self.joinEnds.options = {'HIDDEN'} + + modeOptions: bpy.props.EnumProperty( + items=[ + ("useFaces", "Object from Faces", "Create object from faces", "", 0), + ("useEdges", "Object from Edges", "Create object from edges", "", 1), + ("useVerts", "Object from Vertices", "Create object from vertices", "", 2), + ], + name = "Mode", + #update = modeOptionsChanged, + default = "useVerts", + options = {"SKIP_SAVE"} #if the option is saved, the user will get an error if not selecting the same kind of geometry + ) + + drawEdges: bpy.props.BoolProperty(name="Draw edges", description="Only works for the Object from Vertices mode.\ + If checked, edges are drawn between the vertices", + default=True) + evenOdd: bpy.props.BoolProperty(name="Even-Odd", description="Only works for the Object from Vertices mode.\ +If checked, the edges are created between even vertices and between odd vertices. Need to select at least 4 vertices", + default=False) + joinEnds: bpy.props.BoolProperty(name='Join ends', description='Only works for the Object from Vertices mode.\ + If checked, an edge is added between the first and last selected vertices', default=True) + + def execute(self, context): # execute() is called when running the operator. + import bmesh + bpy.ops.object.mode_set(mode='OBJECT') # needed to update the vertex selection + activeObj = context.view_layer.objects.active + meshCopy = activeObj.data.copy() # keeping the copy of the active object data for the new object + selectedVerts = [] + selectedEdges = [] + selectedFaces = [] + bm = bmesh.new() + selectedObjs = bpy.context.view_layer.objects.selected + originalObjs = [] # cannot use selectedObjs because it is being changed with the selection + copyObjs = [] # copies/duplicates are needed to change the origins without affecting the originals + for o in selectedObjs: + originalObjs.append(o) + for o in originalObjs: + bpy.ops.object.select_all(action='DESELECT') + o.select_set(True) + bpy.ops.object.duplicate() # need to duplicate the mesh to stop affecting the originals + o.select_set(False) + copyObjs.append(bpy.context.view_layer.objects.selected[0]) + bpy.ops.object.select_all(action='DESELECT') + for o in copyObjs: + o.select_set(True) + bpy.ops.object.origin_set(type='ORIGIN_CURSOR') # using to keep the geometry position relative to the cursor + for o in copyObjs: + bm.from_mesh(o.data) + bm.verts.ensure_lookup_table() + for vert in bm.verts: # remove non-selected vertices + if not vert.select: + bm.verts.remove(vert) + if len(bm.select_history) == len(bm.verts): # vertices selected individually, using order of selection + selectedVerts.extend(bm.select_history) + else: selectedVerts.extend(bm.verts) + for edge in bm.edges: + if not edge.select: + bm.edges.remove(edge) + selectedEdges.extend(bm.edges) + for face in bm.faces: + if not face.select: + bm.faces.remove(face) + selectedFaces.extend(bm.faces) + selectedVerts = list(dict.fromkeys(selectedVerts)) # remove duplicates + if len(selectedVerts) < 3: + self.report({'ERROR'}, "You need to select at least 3 vertices, or 2 edges, or 1 face") + for o in copyObjs: # removing copies + bpy.data.objects.remove(o, do_unlink=True) + for o in originalObjs: + o.select_set(True) + bpy.context.view_layer.objects.active = activeObj + bpy.ops.object.mode_set(mode='EDIT') + bm.free() + return {'CANCELLED'} + if self.modeOptions == "useVerts": + bmVerts = bmesh.new() + for v in selectedVerts: + bmVerts.verts.new(v.co) + for v in bmVerts.verts: + v.select_set(True) + bmVerts.verts.ensure_lookup_table() + if self.drawEdges == True: + numVertices = len(bmVerts.verts) + if self.evenOdd == False: + if self.joinEnds == True: + for i in range(numVertices): + bmVerts.edges.new((bmVerts.verts[i-1],bmVerts.verts[i])) + else: + for i in range(numVertices-1): + bmVerts.edges.new((bmVerts.verts[i],bmVerts.verts[i+1])) + else: + if numVertices < 4: + self.report({'ERROR'}, "You need to select at least 4 vertices if the EvenOdd option is enabled") + for o in copyObjs: # removing copies + bpy.data.objects.remove(o, do_unlink=True) + for o in originalObjs: + o.select_set(True) + bpy.context.view_layer.objects.active = activeObj + bpy.ops.object.mode_set(mode='EDIT') + bmVerts.free() + bm.free() + return {'CANCELLED'} + for i in range(numVertices-2): + bmVerts.edges.new((bmVerts.verts[i],bmVerts.verts[i+2])) + if self.joinEnds == True: + bmVerts.edges.new((bmVerts.verts[numVertices-1],bmVerts.verts[0])) + bmVerts.to_mesh(meshCopy) + ob = bpy.data.objects.new('objectFromVertices',meshCopy) + ob.location = context.scene.cursor.location + context.collection.objects.link(ob) + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = ob + if self.modeOptions == "useEdges": + if len(selectedEdges) < 2: + self.report({'ERROR'}, "You need to select at least 2 edges for the Object from Edges mode") + for o in copyObjs: # removing copies + bpy.data.objects.remove(o, do_unlink=True) + for o in originalObjs: + o.select_set(True) + bpy.context.view_layer.objects.active = activeObj + bpy.ops.object.mode_set(mode='EDIT') + bm.free() + return {'CANCELLED'} + bmEdges = bmesh.new() + for e in selectedEdges: + vertsList = [] + for v in e.verts: + bmEdges.verts.new(v.co) + bmEdges.verts.ensure_lookup_table() + vertsList.append(bmEdges.verts[len(bmEdges.verts) - 1]) + bmEdges.edges.new(vertsList) + bmesh.ops.remove_doubles(bmEdges,verts=bmEdges.verts,dist=0.001) + bmEdges.edges.ensure_lookup_table() + for e in bmEdges.edges: + e.select_set(True) + bmEdges.to_mesh(meshCopy) + ob = bpy.data.objects.new('objectFromEdges',meshCopy) + ob.location = context.scene.cursor.location + context.collection.objects.link(ob) + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = ob + if self.modeOptions == "useFaces": + if len(selectedFaces) < 1: + self.report({'ERROR'}, "You need to select at least 1 face for the Object from Faces mode") + for o in copyObjs: # removing copies + bpy.data.objects.remove(o, do_unlink=True) + for o in originalObjs: + o.select_set(True) + bpy.context.view_layer.objects.active = activeObj + bpy.ops.object.mode_set(mode='EDIT') + bm.free() + return {'CANCELLED'} + bmFaces = bmesh.new() + for f in selectedFaces: + vertsList = [] + for v in f.verts: + bmFaces.verts.new(v.co) + bmFaces.verts.ensure_lookup_table() + vertsList.append(bmFaces.verts[len(bmFaces.verts) - 1]) + bmFaces.faces.new(vertsList) + bmesh.ops.remove_doubles(bmFaces,verts=bmFaces.verts,dist=0.001) + bmFaces.faces.ensure_lookup_table() + for f in bmFaces.faces: + f.select_set(True) + bmFaces.to_mesh(meshCopy) + ob = bpy.data.objects.new('objectFromFaces',meshCopy) + ob.location = context.scene.cursor.location + context.collection.objects.link(ob) + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = ob + bpy.ops.object.mode_set(mode='EDIT') + for o in copyObjs: # removing copies + bpy.data.objects.remove(o, do_unlink=True) + bm.free() + return {'FINISHED'} + +def menu_func(self, context): + self.layout.operator(ObjectFromGeometry.bl_idname) + +# store keymaps here to access after registration +addon_keymaps = [] + +def register(): + bpy.utils.register_class(ObjectFromGeometry) + bpy.types.VIEW3D_MT_edit_mesh.append(menu_func) # Adds the new operator to an existing menu. + + # handle the keymap + wm = bpy.context.window_manager + # Note that in background mode (no GUI available), keyconfigs are not available either, + # so we have to check this to avoid nasty errors in background case. + kc = wm.keyconfigs.addon + if kc: + km = wm.keyconfigs.addon.keymaps.new(name='Mesh', space_type='EMPTY') + kmi = km.keymap_items.new(ObjectFromGeometry.bl_idname, 'O', 'PRESS', alt=True) + addon_keymaps.append((km, kmi)) + +def unregister(): + # Note: when unregistering, it's usually good practice to do it in reverse order you registered. + # Can avoid strange issues like keymap still referring to operators already unregistered... + # handle the keymap + for km, kmi in addon_keymaps: + km.keymap_items.remove(kmi) + addon_keymaps.clear() + + bpy.utils.unregister_class(ObjectFromGeometry) + bpy.types.VIEW3D_MT_edit_mesh.remove(menu_func) + + +# This allows you to run the script directly from Blender's Text editor +# to test the add-on without having to install it. +if __name__ == "__main__": + register() \ No newline at end of file