3
11

New Object From Selected Geometry Blender Add-on #16

Open
Giambattista-Caltabiano wants to merge 1 commits from Giambattista-Caltabiano/blender-addons-contrib:giambattista-caltabiano-patch-1 into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.

266
New object from selected.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
#
# ***** 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()