New Object From Selected Geometry Blender Add-on #16
266
New object from selected.py
Normal file
266
New object from selected.py
Normal 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()
|
Reference in New Issue
Block a user