mesh_extra_tools: move to contrib: T63750

This commit is contained in:
Brendon Murphy 2019-05-24 16:03:30 +10:00
parent 01d80b8f60
commit 9e99e90f08
Notes: blender-bot 2023-02-14 18:07:15 +01:00
Referenced by commit 682d48ce: mesh_tools: restore to release: T63750 9e99e90f08
Referenced by commit 682d48ce, mesh_tools: restore to release: T63750 9e99e90f08
33 changed files with 0 additions and 12363 deletions

View File

@ -1,905 +0,0 @@
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# Contributed to by:
# meta-androcto, Hidesato Ikeya, zmj100, Gert De Roost, TrumanBlending, PKHG, #
# Oscurart, Greg, Stanislav Blinov, komi3D, BlenderLab, Paul Marshall (brikbot), #
# metalliandy, macouno, CoDEmanX, dustractor, Liero, lijenstina, Germano Cavalcante #
# Pistiwique, Jimmy Hazevoet #
bl_info = {
"name": "Edit Tools 2",
"author": "meta-androcto",
"version": (0, 3, 4),
"blender": (2, 78, 0),
"location": "View3D > Toolshelf > Tools and Specials (W-key)",
"description": "Extra mesh edit tools - modifying meshes and selection",
"warning": "",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
"Py/Scripts/Modeling/Extra_Tools",
"category": "Mesh"}
# Import From Files
if "bpy" in locals():
import importlib
importlib.reload(face_inset_fillet)
importlib.reload(mesh_filletplus)
importlib.reload(mesh_vertex_chamfer)
importlib.reload(mesh_mextrude_plus)
importlib.reload(mesh_offset_edges)
importlib.reload(pkhg_faces)
importlib.reload(mesh_edge_roundifier)
importlib.reload(mesh_cut_faces)
importlib.reload(split_solidify)
importlib.reload(mesh_edges_floor_plan)
importlib.reload(mesh_edges_length)
importlib.reload(random_vertices)
importlib.reload(mesh_fastloop)
importlib.reload(mesh_edgetools)
importlib.reload(mesh_pen_tool)
importlib.reload(vfe_context_menu)
importlib.reload(mesh_help)
importlib.reload(mesh_select_by_direction)
importlib.reload(mesh_select_by_edge_length)
importlib.reload(mesh_select_by_pi)
importlib.reload(mesh_select_by_type)
importlib.reload(mesh_select_connected_faces)
importlib.reload(mesh_index_select)
importlib.reload(mesh_selection_topokit)
importlib.reload(mesh_info_select)
importlib.reload(mesh_extrude_and_reshape)
importlib.reload(mesh_check)
importlib.reload(vertex_align)
else:
from . import face_inset_fillet
from . import mesh_filletplus
from . import mesh_vertex_chamfer
from . import mesh_mextrude_plus
from . import mesh_offset_edges
from . import pkhg_faces
from . import mesh_edge_roundifier
from . import mesh_cut_faces
from . import split_solidify
from . import mesh_edges_floor_plan
from . import mesh_edges_length
from . import random_vertices
from . import mesh_fastloop
from . import mesh_edgetools
from . import mesh_pen_tool
from . import vfe_context_menu
from . import mesh_help
from . import mesh_extrude_and_reshape
from . import mesh_check
from . import vertex_align
from .mesh_select_tools import mesh_select_by_direction
from .mesh_select_tools import mesh_select_by_edge_length
from .mesh_select_tools import mesh_select_by_pi
from .mesh_select_tools import mesh_select_by_type
from .mesh_select_tools import mesh_select_connected_faces
from .mesh_select_tools import mesh_index_select
from .mesh_select_tools import mesh_selection_topokit
from .mesh_select_tools import mesh_info_select
from . icons.icons import load_icons
import bpy
import bpy_extras.keyconfig_utils
from bpy.types import (
Menu,
Panel,
PropertyGroup,
AddonPreferences,
)
from bpy.props import (
BoolProperty,
BoolVectorProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntVectorProperty,
PointerProperty,
)
# ------ MENUS ------ #
# Define the "Extras" menu
class VIEW3D_MT_edit_mesh_extras(Menu):
bl_idname = "VIEW3D_MT_edit_mesh_extras"
bl_label = "Edit Tools"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
mode = context.tool_settings.mesh_select_mode
if mode[0]:
split = layout.split()
col = split.column()
col.label(text="Vertex", icon="VERTEXSEL")
col.separator()
col.operator("mesh.vertex_chamfer", text="Vertex Chamfer")
col.operator("mesh.random_vertices", text="Random Vertices")
col = split.column()
col.label(text="Utilities", icon="SCRIPTWIN")
col.separator()
col.operator("object_ot.fastloop", text="Fast loop")
col.operator("mesh.flip_normals", text="Normals Flip")
col.operator("mesh.remove_doubles", text="Remove Doubles")
col.operator("mesh.subdivide", text="Subdivide")
col.operator("mesh.dissolve_limited", text="Dissolve Limited")
elif mode[1]:
split = layout.split()
col = split.column()
col.label(text="Edge", icon="EDGESEL")
col.separator()
col.operator("mesh.fillet_plus", text="Edge Fillet Plus")
col.operator("mesh.offset_edges", text="Offset Edges")
col.operator("mesh.edge_roundifier", text="Edge Roundify")
col.operator("object.mesh_edge_length_set", text="Set Edge Length")
col.operator("mesh.edges_floor_plan")
col = split.column()
col.label(text="Utilities", icon="SCRIPTWIN")
col.separator()
col.operator("object_ot.fastloop", text="Fast loop")
col.operator("mesh.flip_normals", text="Normals Flip")
col.operator("mesh.remove_doubles", text="Remove Doubles")
col.operator("mesh.subdivide", text="Subdivide")
col.operator("mesh.dissolve_limited", text="Dissolve Limited")
elif mode[2]:
split = layout.split()
col = split.column()
col.label(text="Face", icon="FACESEL")
col.separator()
col.operator("object.mextrude", text="Multi Extrude")
col.operator("mesh.face_inset_fillet", text="Face Inset Fillet")
col.operator("mesh.extrude_reshape", text="Push/Pull")
col.operator("mesh.add_faces_to_object", text="PKHG Faces")
col.operator("mesh.ext_cut_faces", text="Cut Faces")
col.operator("mesh.split_solidify", text="Split Solidify")
col = split.column()
col.label(text="Utilities", icon="SCRIPTWIN")
col.separator()
col.operator("object_ot.fastloop", text="Fast loop")
col.operator("mesh.flip_normals", text="Normals Flip")
col.operator("mesh.remove_doubles", text="Remove Doubles")
col.operator("mesh.subdivide", text="Subdivide")
col.operator("mesh.dissolve_limited", text="Dissolve Limited")
class EditToolsPanel(Panel):
bl_label = "Mesh Edit Tools"
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
bl_context = "mesh_edit"
bl_category = "Tools"
bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
scene = context.scene
VERTDROP = scene.mesh_extra_tools.UiTabDrop[0]
EDGEDROP = scene.mesh_extra_tools.UiTabDrop[1]
FACEDROP = scene.mesh_extra_tools.UiTabDrop[2]
UTILSDROP = scene.mesh_extra_tools.UiTabDrop[3]
# Change icons depending on the bool state (compliant with the rest of the UI)
icon_active_0 = "TRIA_RIGHT" if not VERTDROP else "TRIA_DOWN"
icon_active_1 = "TRIA_RIGHT" if not EDGEDROP else "TRIA_DOWN"
icon_active_2 = "TRIA_RIGHT" if not FACEDROP else "TRIA_DOWN"
icon_active_3 = "TRIA_RIGHT" if not UTILSDROP else "TRIA_DOWN"
layout = self.layout
# Vert options
box1 = self.layout.box()
col = box1.column(align=True)
row = col.row(align=True)
row.prop(scene.mesh_extra_tools, "UiTabDrop", text="Vertex", index=0, icon=icon_active_0)
if not VERTDROP:
row.menu("mesh.vert_select_tools", icon="RESTRICT_SELECT_OFF", text="")
row.menu("VIEW3D_MT_Select_Vert", icon="VERTEXSEL", text="")
else:
layout = self.layout
row = layout.row()
row.label(text="Vertex Tools:", icon="VERTEXSEL")
row = layout.split(0.8, align=True)
row.operator("mesh.vertex_chamfer", text="Chamfer")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "mesh_vertex_chamfer"
row = layout.split(0.8, align=True)
row.operator("mesh.random_vertices", text="Random Vertices")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "random_vertices"
# Vertex Align Properties And Menu
cen0 = scene.mesh_extra_tools.vert_align_to
layout = self.layout
layout.label(text="Vertex Align:", icon="UV_VERTEXSEL")
# Draw the menu with 2 options
layout.prop(scene.mesh_extra_tools, "vert_align_to", expand=False)
if cen0 == 'vertex':
row = layout.row(align=True)
row.operator("vertex_align.store_id", text="Store Selected Vertex")
row = layout.split(0.8, align=True)
row.operator("vertex_align.align_original", text="Align to Axis")
props = row.operator("mesh.extra_tools_help", icon="LAYER_USED")
props.help_ids = "vertex_align"
props.popup_size = 400
elif cen0 == "coordinates":
layout.prop(scene.mesh_extra_tools, "vert_align_use_stored", toggle=True)
if scene.mesh_extra_tools.vert_align_use_stored:
col = layout.column(align=True)
col.prop(scene.mesh_extra_tools, "vert_align_store_axis", expand=True)
row = layout.split(0.8, align=True)
row.operator("vertex_align.coord_list_id", text="Align Coordinates")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "vertex_align"
# Edge options
box1 = self.layout.box()
col = box1.column(align=True)
row = col.row(align=True)
row.prop(scene.mesh_extra_tools, "UiTabDrop", text="Edge", index=1, icon=icon_active_1)
if not EDGEDROP:
row.menu("mesh.edge_select_tools", icon="RESTRICT_SELECT_OFF", text="")
row.menu("VIEW3D_MT_Select_Edge", icon="EDGESEL", text="")
else:
layout = self.layout
row = layout.row()
row.label(text="Edge Tools:", icon="EDGESEL")
row.menu("VIEW3D_MT_edit_mesh_edgetools", icon="GRID")
row = layout.split(0.8, align=True)
row.operator("mesh.fillet_plus", text="Fillet plus")
props = row.operator("mesh.extra_tools_help", icon="LAYER_USED")
props.help_ids = "mesh_filletplus"
props.popup_size = 400
row = layout.split(0.8, align=True)
row.operator("mesh.offset_edges", text="Offset Edges")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "mesh_offset_edges"
row = layout.split(0.8, align=True)
row.operator("mesh.edge_roundifier", text="Roundify")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "mesh_edge_roundifier"
row = layout.split(0.8, align=True)
row.operator("object.mesh_edge_length_set", text="Set Edge Length")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "mesh_edges_length"
row = layout.split(0.8, align=True)
row.operator("mesh.edges_floor_plan")
props = row.operator("mesh.extra_tools_help", icon="LAYER_USED")
props.help_ids = "mesh_edges_floor_plan"
props.popup_size = 400
# Face options
box1 = self.layout.box()
col = box1.column(align=True)
row = col.row(align=True)
row.prop(scene.mesh_extra_tools, "UiTabDrop", text="Face", index=2, icon=icon_active_2)
if not FACEDROP:
row.menu("mesh.face_select_tools", icon="RESTRICT_SELECT_OFF", text="")
row.menu("VIEW3D_MT_Select_Face", icon="FACESEL", text="")
else:
layout = self.layout
row = layout.row()
row.label(text="Face Tools:", icon="FACESEL")
row = layout.split(0.8, align=True)
row.operator("object.mextrude", text="Multi Extrude")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "mesh_mextrude_plus"
row = layout.split(0.8, align=True)
row.operator("mesh.extrude_reshape", text="Push/Pull")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "mesh_extrude_and_reshape"
row = layout.split(0.8, align=True)
row.operator("mesh.face_inset_fillet", text="Inset Fillet")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "face_inset_fillet"
row = layout.split(0.8, align=True)
row.operator("mesh.ext_cut_faces", text="Cut Faces")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "mesh_cut_faces"
row = layout.split(0.8, align=True)
row.operator("mesh.split_solidify", text="Split Solidify")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "split_solidify"
row = layout.split(0.8, align=True)
row.operator("mesh.add_faces_to_object", "Shape Extrude")
row.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "pkhg_faces"
# Utils options
box1 = self.layout.box()
col = box1.column(align=True)
row = col.row(align=True)
row.prop(scene.mesh_extra_tools, "UiTabDrop", text="Utils", index=3, icon=icon_active_3)
if not UTILSDROP:
row.menu("mesh.utils specials", icon="SOLO_OFF", text="")
row.menu("VIEW3D_MT_Edit_MultiMET", icon="LOOPSEL", text="")
else:
layout = self.layout
row = layout.row()
row.label(text="Utilities:")
row = layout.row()
row = layout.split(0.8, align=True)
row.operator("object_ot.fastloop", text="Fast Loop")
props = row.operator("mesh.extra_tools_help", icon="LAYER_USED")
props.help_ids = "mesh_fastloop"
props.popup_size = 400
col = layout.column(align=True)
col.operator("mesh.flip_normals", text="Normals Flip")
col.operator("mesh.remove_doubles", text="Remove Doubles")
col.operator("mesh.subdivide", text="Subdivide")
col.operator("mesh.dissolve_limited", text="Dissolve Limited")
row = layout.row(align=True)
row.operator("mesh.select_vert_edge_face_index",
icon="VERTEXSEL", text="Select By Index").select_type = 'VERT'
# Mesh Check
layout = self.layout
icons = load_icons()
tris = icons.get("triangles")
ngons = icons.get("ngons")
mesh_check = context.window_manager.mesh_check
icon_active_4 = "TRIA_RIGHT" if not mesh_check.mesh_check_use else "TRIA_DOWN"
row = layout.row()
row = layout.split(0.8, align=True)
row.prop(mesh_check, "mesh_check_use", toggle=True, icon=icon_active_4)
row.operator("mesh.extra_tools_help", icon="LAYER_USED").help_ids = "mesh_check"
if mesh_check.mesh_check_use:
layout = self.layout
row = layout.row(align=True)
row.operator("object.face_type_select", text="Tris",
icon_value=tris.icon_id).face_type = 'tris'
row.operator("object.face_type_select", text="Ngons",
icon_value=ngons.icon_id).face_type = 'ngons'
row = layout.row()
row.prop(mesh_check, "display_faces", text="Display Faces")
if mesh_check.display_faces:
col = layout.column(align=True)
col.prop(mesh_check, "edge_width")
col.prop(mesh_check, "face_opacity")
row = layout.row()
row.label(text="Custom Colors:", icon="COLOR")
col = layout.column().split(factor=0.1, align=True)
col.label(text="", icon_value=tris.icon_id)
col.prop(mesh_check, "custom_tri_color", text="")
col = layout.column().split(factor=0.1, align=True)
col.label(text="", icon_value=ngons.icon_id)
col.prop(mesh_check, "custom_ngons_color", text="")
layout.separator()
row = layout.row(align=True)
if bpy.app.debug:
obj_data = getattr(context.active_object, "data", None)
if obj_data:
row.prop(obj_data, "show_extra_indices",
icon="LINENUMBERS_ON", toggle=True)
if context.mode == 'EDIT_MESH' and not context.space_data.use_occlude_geometry:
row.prop(mesh_check, "finer_lines_behind_use", icon="ORTHO")
# ********** Edit Multiselect **********
class VIEW3D_MT_Edit_MultiMET(Menu):
bl_label = "Multi Select"
bl_description = "Multi Select Modes"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
prop = layout.operator("wm.context_set_value",
text="Vertex Select",
icon='VERTEXSEL')
prop.value = "(True, False, False)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Edge Select",
icon='EDGESEL')
prop.value = "(False, True, False)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Face Select",
icon='FACESEL')
prop.value = "(False, False, True)"
prop.data_path = "tool_settings.mesh_select_mode"
layout.separator()
prop = layout.operator("wm.context_set_value",
text="Vertex and Edge Select",
icon='EDITMODE_HLT')
prop.value = "(True, True, False)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Vertex and Face Select",
icon='ORTHO')
prop.value = "(True, False, True)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Edge and Face Select",
icon='SNAP_FACE')
prop.value = "(False, True, True)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Vertex, Edge and Face Select",
icon='SNAP_VOLUME')
prop.value = "(True, True, True)"
prop.data_path = "tool_settings.mesh_select_mode"
# Select Tools
class VIEW3D_MT_Select_Vert(Menu):
bl_label = "Select Vert"
bl_description = "Vertex Selection Modes"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
prop = layout.operator("wm.context_set_value",
text="Vertex Select",
icon='VERTEXSEL')
prop.value = "(True, False, False)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Vertex and Edge Select",
icon='EDITMODE_HLT')
prop.value = "(True, True, False)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Vertex and Face Select",
icon='ORTHO')
prop.value = "(True, False, True)"
prop.data_path = "tool_settings.mesh_select_mode"
class VIEW3D_MT_Select_Edge(Menu):
bl_label = "Select Edge"
bl_description = "Edge Selection Modes"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
prop = layout.operator("wm.context_set_value",
text="Edge Select",
icon='EDGESEL')
prop.value = "(False, True, False)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Vertex and Edge Select",
icon='EDITMODE_HLT')
prop.value = "(True, True, False)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Edge and Face Select",
icon='SNAP_FACE')
prop.value = "(False, True, True)"
prop.data_path = "tool_settings.mesh_select_mode"
class VIEW3D_MT_Select_Face(Menu):
bl_label = "Select Face"
bl_description = "Face Selection Modes"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
prop = layout.operator("wm.context_set_value",
text="Face Select",
icon='FACESEL')
prop.value = "(False, False, True)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Vertex and Face Select",
icon='ORTHO')
prop.value = "(True, False, True)"
prop.data_path = "tool_settings.mesh_select_mode"
prop = layout.operator("wm.context_set_value",
text="Edge and Face Select",
icon='SNAP_FACE')
prop.value = "(False, True, True)"
prop.data_path = "tool_settings.mesh_select_mode"
class VIEW3D_MT_selectface_edit_mesh_add(Menu):
bl_label = "Select by Face"
bl_idname = "mesh.face_select_tools"
bl_description = "Face Selection Tools"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.label(text="Face Selection Tools", icon="RESTRICT_SELECT_OFF")
layout.separator()
layout.operator("mesh.select_all").action = 'TOGGLE'
layout.operator("mesh.select_all", text="Inverse").action = 'INVERT'
layout.operator("mesh.ext_deselect_boundary", text="Deselect Boundary")
layout.separator()
layout.operator("data.facetype_select", text="Triangles").face_type = "3"
layout.operator("data.facetype_select", text="Quads").face_type = "4"
layout.operator("data.facetype_select", text="Ngons").face_type = "5"
layout.separator()
layout.operator("mesh.select_vert_edge_face_index",
text="By Face Index").select_type = 'FACE'
layout.operator("mesh.select_by_direction", text="By Direction")
layout.operator("mesh.select_by_pi", text="By Pi or e")
layout.operator("mesh.select_connected_faces", text="By Connected Faces")
layout.operator("mesh.conway", text="By Conway's game of life")
layout.separator()
layout.operator("mesh.e2e_efe", text="Neighbors by Face")
layout.operator("mesh.f2f_fvnef", text="Neighbors by Vert not Edge")
class VIEW3D_MT_selectedge_edit_mesh_add(Menu):
bl_label = "Select by Edge"
bl_idname = "mesh.edge_select_tools"
bl_description = "Edge Selection Tools"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.label(text="Edge Selection Tools", icon="RESTRICT_SELECT_OFF")
layout.separator()
layout.operator("mesh.select_all").action = 'TOGGLE'
layout.operator("mesh.select_all", text="Inverse").action = 'INVERT'
layout.separator()
layout.operator("mesh.select_vert_edge_face_index",
text="By Edge Index").select_type = 'EDGE'
layout.operator("mesh.select_by_direction", text="By Direction")
layout.operator("mesh.select_by_pi", text="By Pi or e")
layout.operator("mesh.select_by_edge_length", text="By Edge Length")
layout.separator()
layout.operator("mesh.e2e_eve", text="Neighbors by Vertex")
layout.operator("mesh.e2e_evfe", text="Neighbors by Vertex and Face")
layout.operator("mesh.e2e_efnve", text="Lateral Neighbors")
layout.operator("mesh.e2e_evnfe", text="Longitudinal Edges")
class VIEW3D_MT_selectvert_edit_mesh_add(Menu):
bl_label = "Select by Vert"
bl_idname = "mesh.vert_select_tools"
bl_description = "Vertex Selection Tools"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.label(text="Vertex Selection Tools", icon="RESTRICT_SELECT_OFF")
layout.separator()
layout.operator("mesh.select_all").action = 'TOGGLE'
layout.operator("mesh.select_all", text="Inverse").action = 'INVERT'
layout.separator()
layout.operator("mesh.select_vert_edge_face_index",
text="By Vert Index").select_type = 'VERT'
layout.operator("mesh.select_by_direction", text="By Direction")
layout.operator("mesh.select_by_pi", text="By Pi or e")
layout.separator()
layout.operator("mesh.v2v_by_edge", text="Neighbors by Edge")
layout.operator("mesh.e2e_eve", text="Neighbors by Vertex")
layout.operator("mesh.e2e_efe", text="Neighbors by Face")
layout.operator("mesh.v2v_facewise", text="Neighbors by Face - Edge")
class VIEW3D_MT_utils_context_menu(Menu):
bl_label = "Specials Menu"
bl_idname = "mesh.utils specials"
bl_description = "Utils Quick Specials"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.label(text="Fast Specials")
layout.separator()
layout.menu("VIEW3D_MT_edit_mesh_clean")
layout.separator()
layout.operator("mesh.subdivide", text="Subdivide").smoothness = 0.0
layout.operator("mesh.merge", text="Merge...")
layout.operator("mesh.remove_doubles")
layout.operator("mesh.inset")
layout.operator("mesh.bevel", text="Bevel")
layout.operator("mesh.bridge_edge_loops")
layout.separator()
layout.operator("mesh.normals_make_consistent",
text="Recalculate Outside").inside = False
layout.operator("mesh.normals_make_consistent",
text="Recalculate Inside").inside = True
layout.operator("mesh.flip_normals")
# Define the "Extras" Menu append
class VIEW3D_MT_edit_mesh_all(Menu):
bl_idname = "VIEW3D_MT_edit_mesh_all"
bl_label = "Mesh Edit Tools"
def draw(self, context):
layout = self.layout
layout.menu("VIEW3D_MT_edit_mesh_extras")
layout.menu("VIEW3D_MT_edit_mesh_edgetools")
def menu_func(self, context):
self.layout.menu("VIEW3D_MT_edit_mesh_extras")
self.layout.menu("VIEW3D_MT_edit_mesh_edgetools")
# Define "Select" Menu append
def menu_select(self, context):
if context.tool_settings.mesh_select_mode[2]:
self.layout.menu("mesh.face_select_tools", icon="FACESEL")
if context.tool_settings.mesh_select_mode[1]:
self.layout.menu("mesh.edge_select_tools", icon="EDGESEL")
if context.tool_settings.mesh_select_mode[0]:
self.layout.menu("mesh.vert_select_tools", icon="VERTEXSEL")
# Scene Properties
class MeshExtraToolsSceneProps(PropertyGroup):
# Define the UI drop down prop
UiTabDrop = BoolVectorProperty(
name="Tab",
description="Expand/Collapse UI elements",
default=(False,) * 4,
size=4,
)
# Vertex align
vert_align_store_axis: FloatVectorProperty(
name="Define Custom Coordinates",
description="Store the values of coordinates, for repeated use\n"
"as a starting point",
default=(0.0, 0.0, 0.0),
min=-100.0, max=100.0,
step=1, size=3,
subtype='XYZ',
precision=3
)
vert_align_use_stored: BoolProperty(
name="Use Stored Coordinates",
description="Use starting point coordinates for alignment",
default=False
)
vert_align_to: EnumProperty(
items=(('vertex', "Original vertex",
"Use the stored vertex coordinates for aligning"),
('coordinates', "Custom coordinates",
"Use defined custom coordinates for aligning")),
name="Align to",
default='vertex'
)
vert_align_axis = BoolVectorProperty(
name="Axis",
description="Align to a specific Axis",
default=(True, False, False),
size=3,
)
# Mesh Info select
mesh_info_show: BoolProperty(
name="Show Face Info",
description="Display the Object's Face Count information\n"
"Note: it can have some performance impact on dense meshes\n"
"Leave it closed if not needed or set the Delay to a higher value",
default=False
)
mesh_info_delay: FloatProperty(
name="Delay",
description="Set the Update time Delay in seconds\n"
"Set to zero to update with the UI refresh\n"
"Higher values will sometimes need to hover over the cursor",
default=2.0,
min=0.0, max=20.0,
step=100,
subtype='TIME',
precision=1
)
# Add-on Preferences
class mesh_extra_tools_pref(AddonPreferences):
bl_idname = __name__
show_info: BoolProperty(
name="Info",
default=False,
description="Some general information about the add-on",
)
show_shortcuts: BoolProperty(
name="Hot Keys",
default=False,
description="List of the shortcuts used for the included various tools",
)
def draw(self, context):
layout = self.layout
box = layout.box()
box.prop(self, "show_info", icon="INFO")
if self.show_info:
box.label(text="Collection of various extra Mesh Edit Functions",
icon="LAYER_ACTIVE")
box.label(text="The majority of the tools can be found in"
"Mesh Edit Mode Toolshelf or W key Specials Menu",
icon="LAYER_USED")
box.label(text="The Pen tool is a separate Panel in the Toolshelf",
icon="LAYER_USED")
box.label(text="The Face Extrude tool is only available in Object Mode "
"as a separate panel in the Toolshelf",
icon="LAYER_USED")
box.label(text="Face Info / Select is a separate Panel located in Properties > Data Editor",
icon="LAYER_USED")
box.prop(self, "show_shortcuts", icon="KEYINGSET")
if self.show_shortcuts:
col = box.column()
col.label(text="Double Right Click in Edit mode in the 3D Viewport",
icon="LAYER_ACTIVE")
col.label(text="Used for quick access to the Vertex, Edge and Face context menus",
icon="LAYER_USED")
col.separator()
col.label(text="W-key in Edit Mode in the 3D Viewport",
icon="LAYER_ACTIVE")
col.label(text="Tools are grouped into menus prepended to the Specials Menu",
icon="LAYER_USED")
col.separator()
col.label(text="Ctrl+D in Edit Mode in the 3D Viewport",
icon="LAYER_ACTIVE")
col.label(text="Used by the Pen Tool to start drawing. When activated:",
icon="LAYER_USED")
col.label(text="Shift + Mouse Move is used to draw along the X axis",
icon="LAYER_USED")
col.label(text="Alt + Mouse Move is used to draw along the Y axis",
icon="LAYER_USED")
col.separator()
col.label(text="Note: when using Fast Loop operator, press Esc twice to finish",
icon="LAYER_ACTIVE")
def register():
mesh_pen_tool.register()
vfe_context_menu.register()
mesh_extrude_and_reshape.register()
mesh_check.register()
bpy.utils.register_module(__name__)
# Register Scene Properties
bpy.types.Scene.mesh_extra_tools = PointerProperty(
type=MeshExtraToolsSceneProps
)
# Used in mesh_selection_topokit to store cache selection data
bpy.types.Object.tkkey = IntVectorProperty(size=4)
# Add "Extras" menu to the "W-key Specials" menu
bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_func)
bpy.types.VIEW3D_MT_select_edit_mesh.prepend(menu_select)
try:
bpy.types.VIEW3D_MT_Select_Edit_Mesh.prepend(menu_select)
except:
pass
def unregister():
mesh_pen_tool.unregister()
vfe_context_menu.unregister()
mesh_extrude_and_reshape.unregister()
mesh_check.unregister()
del bpy.types.Scene.mesh_extra_tools
del bpy.types.Object.tkkey
bpy.utils.unregister_module(__name__)
# Remove "Extras" menu from the "" menu.
bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_func)
bpy.types.VIEW3D_MT_select_edit_mesh.remove(menu_select)
try:
bpy.types.VIEW3D_MT_Select_Edit_Mesh.remove(menu_select)
except:
pass
if __name__ == "__main__":
register()

View File

@ -1,335 +0,0 @@
# -*- coding: utf-8 -*-
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# based completely on addon by zmj100
# added some distance limits to prevent overlap - max12345
import bpy
import bmesh
from bpy.types import Operator
from bpy.props import (
FloatProperty,
IntProperty,
BoolProperty,
EnumProperty,
)
from math import (
sin, cos, tan,
degrees, radians,
)
from mathutils import Matrix
def edit_mode_out():
bpy.ops.object.mode_set(mode='OBJECT')
def edit_mode_in():
bpy.ops.object.mode_set(mode='EDIT')
def angle_rotation(rp, q, axis, angle):
# returns the vector made by the rotation of the vector q
# rp by angle around axis and then adds rp
return (Matrix.Rotation(angle, 3, axis) * (q - rp)) + rp
def face_inset_fillet(bme, face_index_list, inset_amount, distance,
number_of_sides, out, radius, type_enum, kp):
list_del = []
for faceindex in face_index_list:
bme.faces.ensure_lookup_table()
# loops through the faces...
f = bme.faces[faceindex]
f.select_set(False)
list_del.append(f)
f.normal_update()
vertex_index_list = [v.index for v in f.verts]
dict_0 = {}
orientation_vertex_list = []
n = len(vertex_index_list)
for i in range(n):
# loops through the vertices
dict_0[i] = []
bme.verts.ensure_lookup_table()
p = (bme.verts[vertex_index_list[i]].co).copy()
p1 = (bme.verts[vertex_index_list[(i - 1) % n]].co).copy()
p2 = (bme.verts[vertex_index_list[(i + 1) % n]].co).copy()
# copies some vert coordinates, always the 3 around i
dict_0[i].append(bme.verts[vertex_index_list[i]])
# appends the bmesh vert of the appropriate index to the dict
vec1 = p - p1
vec2 = p - p2
# vectors for the other corner points to the cornerpoint
# corresponding to i / p
angle = vec1.angle(vec2)
adj = inset_amount / tan(angle * 0.5)
h = (adj ** 2 + inset_amount ** 2) ** 0.5
if round(degrees(angle)) == 180 or round(degrees(angle)) == 0.0:
# if the corner is a straight line...
# I think this creates some new points...
if out is True:
val = ((f.normal).normalized() * inset_amount)
else:
val = -((f.normal).normalized() * inset_amount)
p6 = angle_rotation(p, p + val, vec1, radians(90))
else:
# if the corner is an actual corner
val = ((f.normal).normalized() * h)
if out is True:
# this -(p - (vec2.normalized() * adj))) is just the freaking axis afaik...
p6 = angle_rotation(
p, p + val,
-(p - (vec2.normalized() * adj)),
-radians(90)
)
else:
p6 = angle_rotation(
p, p - val,
((p - (vec1.normalized() * adj)) - (p - (vec2.normalized() * adj))),
-radians(90)
)
orientation_vertex_list.append(p6)
new_inner_face = []
orientation_vertex_list_length = len(orientation_vertex_list)
ovll = orientation_vertex_list_length
for j in range(ovll):
q = orientation_vertex_list[j]
q1 = orientation_vertex_list[(j - 1) % ovll]
q2 = orientation_vertex_list[(j + 1) % ovll]
# again, these are just vectors between somewhat displaced corner vertices
vec1_ = q - q1
vec2_ = q - q2
ang_ = vec1_.angle(vec2_)
# the angle between them
if round(degrees(ang_)) == 180 or round(degrees(ang_)) == 0.0:
# again... if it's really a line...
v = bme.verts.new(q)
new_inner_face.append(v)
dict_0[j].append(v)
else:
# s.a.
if radius is False:
h_ = distance * (1 / cos(ang_ * 0.5))
d = distance
elif radius is True:
h_ = distance / sin(ang_ * 0.5)
d = distance / tan(ang_ * 0.5)
# max(d) is vec1_.magnitude * 0.5
# or vec2_.magnitude * 0.5 respectively
# only functional difference v
if d > vec1_.magnitude * 0.5:
d = vec1_.magnitude * 0.5
if d > vec2_.magnitude * 0.5:
d = vec2_.magnitude * 0.5
# only functional difference ^
q3 = q - (vec1_.normalized() * d)
q4 = q - (vec2_.normalized() * d)
# these are new verts somewhat offset from the corners
rp_ = q - ((q - ((q3 + q4) * 0.5)).normalized() * h_)
# reference point inside the curvature
axis_ = vec1_.cross(vec2_)
# this should really be just the face normal
vec3_ = rp_ - q3
vec4_ = rp_ - q4
rot_ang = vec3_.angle(vec4_)
cornerverts = []
for o in range(number_of_sides + 1):
# this calculates the actual new vertices
q5 = angle_rotation(rp_, q4, axis_, rot_ang * o / number_of_sides)
v = bme.verts.new(q5)
# creates new bmesh vertices from it
bme.verts.index_update()
dict_0[j].append(v)
cornerverts.append(v)
cornerverts.reverse()
new_inner_face.extend(cornerverts)
if out is False:
f = bme.faces.new(new_inner_face)
f.select_set(True)
elif out is True and kp is True:
f = bme.faces.new(new_inner_face)
f.select_set(True)
n2_ = len(dict_0)
# these are the new side faces, those that don't depend on cornertype
for o in range(n2_):
list_a = dict_0[o]
list_b = dict_0[(o + 1) % n2_]
bme.faces.new([list_a[0], list_b[0], list_b[-1], list_a[1]])
bme.faces.index_update()
# cornertype 1 - ngon faces
if type_enum == 'opt0':
for k in dict_0:
if len(dict_0[k]) > 2:
bme.faces.new(dict_0[k])
bme.faces.index_update()
# cornertype 2 - triangulated faces
if type_enum == 'opt1':
for k_ in dict_0:
q_ = dict_0[k_][0]
dict_0[k_].pop(0)
n3_ = len(dict_0[k_])
for kk in range(n3_ - 1):
bme.faces.new([dict_0[k_][kk], dict_0[k_][(kk + 1) % n3_], q_])
bme.faces.index_update()
del_ = [bme.faces.remove(f) for f in list_del]
if del_:
del del_
# Operator
class MESH_OT_face_inset_fillet(Operator):
bl_idname = "mesh.face_inset_fillet"
bl_label = "Face Inset Fillet"
bl_description = ("Inset selected and Fillet (make round) the corners \n"
"of the newly created Faces")
bl_options = {"REGISTER", "UNDO"}
# inset amount
inset_amount: FloatProperty(
name="Inset amount",
description="Define the size of the Inset relative to the selection",
default=0.04,
min=0, max=100.0,
step=1,
precision=3
)
# number of sides
number_of_sides: IntProperty(
name="Number of sides",
description="Define the roundness of the corners by specifying\n"
"the subdivision count",
default=4,
min=1, max=100,
step=1
)
distance: FloatProperty(
name="",
description="Use distance or radius for corners' size calculation",
default=0.04,
min=0.00001, max=100.0,
step=1,
precision=3
)
out: BoolProperty(
name="Outside",
description="Inset the Faces outwards in relation to the selection\n"
"Note: depending on the geometry, can give unsatisfactory results",
default=False
)
radius: BoolProperty(
name="Radius",
description="Use radius for corners' size calculation",
default=False
)
type_enum: EnumProperty(
items=(('opt0', "N-gon", "N-gon corners - Keep the corner Faces uncut"),
('opt1', "Triangle", "Triangulate corners")),
name="Corner Type",
default="opt0"
)
kp: BoolProperty(
name="Keep faces",
description="Do not delete the inside Faces\n"
"Only available if the Out option is checked",
default=False
)
def draw(self, context):
layout = self.layout
layout.label(text="Corner Type:")
row = layout.row()
row.prop(self, "type_enum", text="")
row = layout.row(align=True)
row.prop(self, "out")
if self.out is True:
row.prop(self, "kp")
row = layout.row()
row.prop(self, "inset_amount")
row = layout.row()
row.prop(self, "number_of_sides")
row = layout.row()
row.prop(self, "radius")
row = layout.row()
dist_rad = "Radius" if self.radius else "Distance"
row.prop(self, "distance", text=dist_rad)
def execute(self, context):
# this really just prepares everything for the main function
inset_amount = self.inset_amount
number_of_sides = self.number_of_sides
distance = self.distance
out = self.out
radius = self.radius
type_enum = self.type_enum
kp = self.kp
edit_mode_out()
ob_act = context.active_object
bme = bmesh.new()
bme.from_mesh(ob_act.data)
# this
face_index_list = [f.index for f in bme.faces if f.select and f.is_valid]
if len(face_index_list) == 0:
self.report({'WARNING'},
"No suitable Face selection found. Operation cancelled")
edit_mode_in()
return {'CANCELLED'}
elif len(face_index_list) != 0:
face_inset_fillet(bme, face_index_list,
inset_amount, distance, number_of_sides,
out, radius, type_enum, kp)
bme.to_mesh(ob_act.data)
edit_mode_in()
return {'FINISHED'}

View File

@ -1,34 +0,0 @@
import os
import bpy
import bpy.utils.previews
mesh_check_icon_collections = {}
mesh_check_icons_loaded = False
def load_icons():
global mesh_check_icon_collections
global mesh_check_icons_loaded
if mesh_check_icons_loaded:
return mesh_check_icon_collections["main"]
custom_icons = bpy.utils.previews.new()
icons_dir = os.path.join(os.path.dirname(__file__))
custom_icons.load("ngons", os.path.join(icons_dir, "ngon.png"), 'IMAGE')
custom_icons.load("triangles", os.path.join(icons_dir, "triangle.png"), 'IMAGE')
mesh_check_icon_collections["main"] = custom_icons
mesh_check_icons_loaded = True
return mesh_check_icon_collections["main"]
def clear_icons():
global mesh_check_icons_loaded
for icon in mesh_check_icon_collections.values():
bpy.utils.previews.remove(icon)
mesh_check_icon_collections.clear()
mesh_check_icons_loaded = False

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,370 +0,0 @@
# gpl author: Pistiwique
bl_info = {
"name": "Mesh Check BGL edition",
"description": "Display the triangles and ngons of the mesh",
"author": "Pistiwique",
"version": (1, 0, 1),
"blender": (2, 75, 0),
"location": "3D View(s) > Properties > Shading",
"category": "3D View"
}
import bpy
import bmesh
from bgl import (
glBegin,
glLineWidth,
glColor4f,
glVertex3f,
glEnd,
GL_LINES,
glEnable,
glDisable,
GL_DEPTH_TEST,
GL_BLEND,
GL_POLYGON
)
from mathutils.geometry import tessellate_polygon as tessellate
from bpy.types import (
Operator,
PropertyGroup,
)
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
PointerProperty,
)
# -- Globals -- #
mesh_check_handle = []
draw_enabled = [False]
edge_width = [1.0]
face_opacity = [0.2]
edges_tri_color = [(1.0, 1.0, 0.0, 1)]
faces_tri_color = [(1.0, 1.0, 0.0, face_opacity[0])]
edges_ngons_color = [(1.0, 0.0, 0.0, 1.0)]
faces_ngons_color = [(1.0, 0.0, 0.0, face_opacity[0])]
bm_old = [None]
finer_lines = [False]
def draw_poly(points):
for i in range(len(points)):
glVertex3f(points[i][0], points[i][1], points[i][2])
def mesh_check_draw_callback():
obj = bpy.context.object
if obj and obj.type == 'MESH':
if draw_enabled[0]:
mesh = obj.data
matrix_world = obj.matrix_world
glLineWidth(edge_width[0])
if bpy.context.mode == 'EDIT_MESH':
use_occlude = True
if bm_old[0] is None or not bm_old[0].is_valid:
bm = bm_old[0] = bmesh.from_edit_mesh(mesh)
else:
bm = bm_old[0]
no_depth = not bpy.context.space_data.use_occlude_geometry
if no_depth:
glDisable(GL_DEPTH_TEST)
use_occlude = False
if finer_lines[0]:
glLineWidth(edge_width[0] / 4.0)
use_occlude = True
for face in bm.faces:
if len([verts for verts in face.verts]) == 3:
faces = [matrix_world * vert.co for vert in face.verts]
glColor4f(*faces_tri_color[0])
glEnable(GL_BLEND)
glBegin(GL_POLYGON)
draw_poly(faces)
glEnd()
for edge in face.edges:
if edge.is_valid:
edges = [matrix_world * vert.co for vert in edge.verts]
glColor4f(*edges_tri_color[0])
glBegin(GL_LINES)
draw_poly(edges)
glEnd()
elif len([verts for verts in face.verts]) > 4:
new_faces = []
faces = []
coords = [v.co for v in face.verts]
indices = [v.index for v in face.verts]
for pol in tessellate([coords]):
new_faces.append([indices[i] for i in pol])
for f in new_faces:
faces.append(
[((matrix_world * bm.verts[i].co)[0] + face.normal.x * 0.001,
(matrix_world * bm.verts[i].co)[1] + face.normal.y * 0.001,
(matrix_world * bm.verts[i].co)[2] + face.normal.z * 0.001)
for i in f]
)
for f in faces:
glColor4f(*faces_ngons_color[0])
glEnable(GL_BLEND)
glBegin(GL_POLYGON)
draw_poly(f)
glEnd()
for edge in face.edges:
if edge.is_valid:
edges = [matrix_world * vert.co for vert in edge.verts]
glColor4f(*edges_ngons_color[0])
glBegin(GL_LINES)
draw_poly(edges)
glEnd()
glDisable(GL_BLEND)
glColor4f(0.0, 0.0, 0.0, 1.0)
glLineWidth(edge_width[0])
glEnable(GL_DEPTH_TEST)
if use_occlude:
for face in bm.faces:
if len([verts for verts in face.verts]) == 3:
faces = []
for vert in face.verts:
vert_face = matrix_world * vert.co
faces.append(
(vert_face[0] + face.normal.x * 0.001,
vert_face[1] + face.normal.y * 0.001,
vert_face[2] + face.normal.z * 0.001)
)
glColor4f(*faces_tri_color[0])
glEnable(GL_BLEND)
glBegin(GL_POLYGON)
draw_poly(faces)
glEnd()
for edge in face.edges:
if edge.is_valid:
edges = []
for vert in edge.verts:
vert_edge = matrix_world * vert.co
edges.append(
(vert_edge[0] + face.normal.x * 0.001,
vert_edge[1] + face.normal.y * 0.001,
vert_edge[2] + face.normal.z * 0.001)
)
glColor4f(*edges_tri_color[0])
glBegin(GL_LINES)
draw_poly(edges)
glEnd()
elif len([verts for verts in face.verts]) > 4:
new_faces = []
faces = []
coords = [v.co for v in face.verts]
indices = [v.index for v in face.verts]
for pol in tessellate([coords]):
new_faces.append([indices[i] for i in pol])
for f in new_faces:
faces.append([
((matrix_world * bm.verts[i].co)[0] + face.normal.x * 0.001,
(matrix_world * bm.verts[i].co)[1] + face.normal.y * 0.001,
(matrix_world * bm.verts[i].co)[2] + face.normal.z * 0.001)
for i in f]
)
for f in faces:
glColor4f(*faces_ngons_color[0])
glEnable(GL_BLEND)
glBegin(GL_POLYGON)
draw_poly(f)
glEnd()
for edge in face.edges:
if edge.is_valid:
edges = []
for vert in edge.verts:
vert_edge = matrix_world * vert.co
edges.append(
(vert_edge[0] + face.normal.x * 0.001,
vert_edge[1] + face.normal.y * 0.001,
vert_edge[2] + face.normal.z * 0.001)
)
glColor4f(*edges_ngons_color[0])
glBegin(GL_LINES)
draw_poly(edges)
glEnd()
glDisable(GL_BLEND)
glColor4f(0.0, 0.0, 0.0, 1.0)
def updateBGLData(self, context):
if self.mesh_check_use and self.display_faces:
bpy.ops.object.mode_set(mode='EDIT')
draw_enabled[0] = True
edge_width[0] = self.edge_width
finer_lines[0] = self.finer_lines_behind_use
face_opacity[0] = self.face_opacity
edges_tri_color[0] = (
self.custom_tri_color[0],
self.custom_tri_color[1],
self.custom_tri_color[2],
1)
faces_tri_color[0] = (
self.custom_tri_color[0],
self.custom_tri_color[1],
self.custom_tri_color[2],
self.face_opacity
)
edges_ngons_color[0] = (
self.custom_ngons_color[0],
self.custom_ngons_color[1],
self.custom_ngons_color[2],
1)
faces_ngons_color[0] = (
self.custom_ngons_color[0],
self.custom_ngons_color[1],
self.custom_ngons_color[2],
self.face_opacity
)
return
draw_enabled[0] = False
class FaceTypeSelect(Operator):
bl_idname = "object.face_type_select"
bl_label = "Face type select"
bl_description = "Select Triangles and / or Ngons on the Active Object"
bl_options = {'REGISTER', 'UNDO'}
face_type: EnumProperty(
name="Face Type",
items=(('tris', "Tris", "Colorize Triangles in the Mesh"),
('ngons', "Ngons", "Colorize Ngons in the Mesh")),
default='ngons'
)
@classmethod
def poll(cls, context):
return context.active_object is not None and context.active_object.type == 'MESH'
def execute(self, context):
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
context.tool_settings.mesh_select_mode = (False, False, True)
if self.face_type == "tris":
bpy.ops.mesh.select_face_by_sides(number=3, type='EQUAL')
else:
bpy.ops.mesh.select_face_by_sides(number=4, type='GREATER')
return {'FINISHED'}
class MeshCheckCollectionGroup(PropertyGroup):
mesh_check_use: BoolProperty(
name="Mesh Check",
description="Display Mesh Check options",
default=False,
update=updateBGLData
)
display_faces: BoolProperty(
name="Display Faces",
description="Use BGL to display Ngons and Tris of the mesh",
default=False,
update=updateBGLData
)
edge_width: FloatProperty(
name="Width",
description="Drawn Edges width in pixels",
min=1.0,
max=10.0,
default=3.0,
subtype='PIXEL',
update=updateBGLData
)
finer_lines_behind_use: BoolProperty(
name="Finer Lines behind",
description="Display partially hidden edges finer in non-occlude mode",
default=True,
update=updateBGLData
)
custom_tri_color: FloatVectorProperty(
name="Tri Color",
description="Custom color for the Triangles",
min=0.0,
max=1.0,
default=(1.0, 1.0, 0.0),
size=3,
subtype='COLOR',
update=updateBGLData
)
custom_ngons_color: FloatVectorProperty(
name="Ngons Color",
description="Custom color for the Ngons",
min=0.0,
max=1.0,
default=(1.0, 0.0, 0.0),
size=3,
subtype='COLOR',
update=updateBGLData
)
face_opacity: FloatProperty(
name="Face Opacity",
description="Opacity of the color for the face",
min=0.0,
max=1.0,
default=0.2,
subtype='FACTOR',
update=updateBGLData
)
# Register
classes = (
FaceTypeSelect,
MeshCheckCollectionGroup,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.WindowManager.mesh_check = PointerProperty(
type=MeshCheckCollectionGroup
)
if mesh_check_handle:
bpy.types.SpaceView3D.draw_handler_remove(mesh_check_handle[0], 'WINDOW')
mesh_check_handle[:] = [bpy.types.SpaceView3D.draw_handler_add(mesh_check_draw_callback,
(), 'WINDOW', 'POST_VIEW')]
def unregister():
del bpy.types.WindowManager.mesh_check
if mesh_check_handle:
bpy.types.SpaceView3D.draw_handler_remove(mesh_check_handle[0], 'WINDOW')
mesh_check_handle[:] = []
for cls in classes:
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()

View File

@ -1,266 +0,0 @@
# gpl author: Stanislav Blinov
bl_info = {
"name": "Cut Faces",
"author": "Stanislav Blinov",
"version": (1, 0, 0),
"blender": (2, 72, 0),
"description": "Cut Faces and Deselect Boundary operators",
"category": "Mesh", }
import bpy
import bmesh
from bpy.types import Operator
from bpy.props import (
BoolProperty,
IntProperty,
EnumProperty,
)
def bmesh_from_object(object):
mesh = object.data
if object.mode == 'EDIT':
bm = bmesh.from_edit_mesh(mesh)
else:
bm = bmesh.new()
bm.from_mesh(mesh)
return bm
def bmesh_release(bm, object):
mesh = object.data
bm.select_flush_mode()
if object.mode == 'EDIT':
bmesh.update_edit_mesh(mesh, True)
else:
bm.to_mesh(mesh)
bm.free()
def calc_face(face, keep_caps=True):
assert face.tag
def radial_loops(loop):
next = loop.link_loop_radial_next
while next != loop:
result, next = next, next.link_loop_radial_next
yield result
result = []
face.tag = False
selected = []
to_select = []
for loop in face.loops:
self_selected = False
# Iterate over selected adjacent faces
for radial_loop in filter(lambda l: l.face.select, radial_loops(loop)):
# Tag the edge if no other face done so already
if not loop.edge.tag:
loop.edge.tag = True
self_selected = True
adjacent_face = radial_loop.face
# Only walk adjacent face if current face tagged the edge
if adjacent_face.tag and self_selected:
result += calc_face(adjacent_face, keep_caps)
if loop.edge.tag:
(selected, to_select)[self_selected].append(loop)
for loop in to_select:
result.append(loop.edge)
selected.append(loop)
# Select opposite edge in quads
if keep_caps and len(selected) == 1 and len(face.verts) == 4:
result.append(selected[0].link_loop_next.link_loop_next.edge)
return result
def get_edge_rings(bm, keep_caps=True):
def tag_face(face):
if face.select:
face.tag = True
for edge in face.edges:
edge.tag = False
return face.select
# fetch selected faces while setting up tags
selected_faces = [f for f in bm.faces if tag_face(f)]
edges = []
try:
# generate a list of edges to select:
# traversing only tagged faces, since calc_face can walk and untag islands
for face in filter(lambda f: f.tag, selected_faces):
edges += calc_face(face, keep_caps)
finally:
# housekeeping: clear tags
for face in selected_faces:
face.tag = False
for edge in face.edges:
edge.tag = False
return edges
class MESH_xOT_deselect_boundary(Operator):
bl_idname = "mesh.ext_deselect_boundary"
bl_label = "Deselect Boundary"
bl_description = ("Deselect boundary edges of selected faces\n"
"Note: if all Faces are selected there is no boundary,\n"
"so the tool will not have results")
bl_options = {'REGISTER', 'UNDO'}
keep_cap_edges: BoolProperty(
name="Keep Cap Edges",
description="Keep quad strip cap edges selected",
default=False
)
@classmethod
def poll(cls, context):
active_object = context.active_object
return active_object and active_object.type == 'MESH' and active_object.mode == 'EDIT'
def execute(self, context):
object = context.active_object
bm = bmesh_from_object(object)
try:
edges = get_edge_rings(bm, keep_caps=self.keep_cap_edges)
if not edges:
self.report({'WARNING'}, "No suitable Face selection found. Operation cancelled")
return {'CANCELLED'}
bpy.ops.mesh.select_all(action='DESELECT')
bm.select_mode = {'EDGE'}
for edge in edges:
edge.select = True
context.tool_settings.mesh_select_mode[:] = False, True, False
finally:
bmesh_release(bm, object)
return {'FINISHED'}
class MESH_xOT_cut_faces(Operator):
bl_idname = "mesh.ext_cut_faces"
bl_label = "Cut Faces"
bl_description = "Cut selected faces, connected through their adjacent edges"
bl_options = {'REGISTER', 'UNDO'}
# from bmesh_operators.h
SUBD_INNERVERT = 0
SUBD_PATH = 1
SUBD_FAN = 2
SUBD_STRAIGHT_CUT = 3
num_cuts: IntProperty(
name="Number of Cuts",
default=1,
min=1,
max=100,
subtype='UNSIGNED'
)
use_single_edge: BoolProperty(
name="Quad/Tri Mode",
description="Cut boundary faces",
default=False
)
corner_type: EnumProperty(
items=[('SUBD_INNERVERT', "Inner Vert", ""),
('SUBD_PATH', "Path", ""),
('SUBD_FAN', "Fan", ""),
('SUBD_STRAIGHT_CUT', "Straight Cut", ""),
],
name="Quad Corner Type",
description="How to subdivide quad corners",
default='SUBD_STRAIGHT_CUT'
)
use_grid_fill: BoolProperty(
name="Use Grid Fill",
description="Fill fully enclosed faces with a grid",
default=True
)
@classmethod
def poll(cls, context):
active_object = context.active_object
return active_object and active_object.type == 'MESH' and active_object.mode == 'EDIT'
def draw(self, context):
layout = self.layout
layout.label(text="Number of Cuts:")
layout.prop(self, "num_cuts", text="")
layout.prop(self, "use_single_edge")
layout.prop(self, "use_grid_fill")
layout.label(text="Quad Corner Type:")
layout.prop(self, "corner_type", text="")
def cut_edges(self, context):
object = context.active_object
bm = bmesh_from_object(object)
try:
edges = get_edge_rings(bm, keep_caps=True)
if not edges:
self.report({'WARNING'},
"No suitable Face selection found. Operation cancelled")
return False
result = bmesh.ops.subdivide_edges(
bm,
edges=edges,
cuts=int(self.num_cuts),
use_grid_fill=bool(self.use_grid_fill),
use_single_edge=bool(self.use_single_edge),
quad_corner_type=eval("self." + self.corner_type)
)
bpy.ops.mesh.select_all(action='DESELECT')
bm.select_mode = {'EDGE'}
inner = result['geom_inner']
for edge in filter(lambda e: isinstance(e, bmesh.types.BMEdge), inner):
edge.select = True
finally:
bmesh_release(bm, object)
return True
def execute(self, context):
if not self.cut_edges(context):
return {'CANCELLED'}
context.tool_settings.mesh_select_mode[:] = False, True, False
# Try to select all possible loops
bpy.ops.mesh.loop_multi_select(ring=False)
return {'FINISHED'}
def register():
bpy.utils.register_class(MESH_xOT_deselect_boundary)
bpy.utils.register_class(MESH_xOT_cut_faces)
def unregister():
bpy.utils.unregister_class(MESH_xOT_deselect_boundary)
bpy.utils.unregister_class(MESH_xOT_cut_faces)
if __name__ == "__main__":
register()

File diff suppressed because it is too large Load Diff

View File

@ -1,384 +0,0 @@
# ##### 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; version 2
# of the License.
#
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# based upon the functionality of Mesh to wall by luxuy_BlenderCN
# thanks to meta-androcto
bl_info = {
"name": "Edge Floor Plan",
"author": "lijenstina",
"version": (0, 2),
"blender": (2, 78, 0),
"location": "View3D > EditMode > Mesh",
"description": "Make a Floor Plan from Edges",
"wiki_url": "",
"category": "Mesh"}
import bpy
import bmesh
from bpy.types import Operator
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
)
# Handle error notifications
def error_handlers(self, error, reports="ERROR"):
if self and reports:
self.report({'WARNING'}, reports + " (See Console for more info)")
print("\n[mesh.edges_floor_plan]\nError: {}\n".format(error))
class MESH_OT_edges_floor_plan(Operator):
bl_idname = "mesh.edges_floor_plan"
bl_label = "Edges Floor Plan"
bl_description = "Top View, Extrude Flat Along Edges"
bl_options = {'REGISTER', 'UNDO'}
wid: FloatProperty(
name="Wall width:",
description="Set the width of the generated walls\n",
default=0.1,
min=0.001, max=30000
)
depth: FloatProperty(
name="Inner height:",
description="Set the height of the inner wall edges",
default=0.0,
min=0, max=10
)
connect_ends: BoolProperty(
name="Connect Ends",
description="Connect the ends of the boundary Edge loops",
default=False
)
repeat_cleanup: IntProperty(
name="Recursive Prepare",
description="Number of times that the preparation phase runs\n"
"at the start of the script\n"
"If parts of the mesh are not modified, increase this value",
min=1, max=20,
default=1
)
fill_items = [
('EDGE_NET', "Edge Net",
"Edge Net Method for mesh preparation - Initial Fill\n"
"The filled in faces will be Inset individually\n"
"Supports simple 3D objects"),
('SINGLE_FACE', "Single Face",
"Single Face Method for mesh preparation - Initial Fill\n"
"The produced face will be Triangulated before Inset Region\n"
"Good for edges forming a circle, avoid 3D objects"),
('SOLIDIFY', "Solidify",
"Extrude and Solidify Method\n"
"Useful for complex meshes, however works best on flat surfaces\n"
"as the extrude direction has to be defined")
]
fill_type: EnumProperty(
name="Fill Type",
items=fill_items,
description="Choose the method for creating geometry",
default='SOLIDIFY'
)
keep_faces: BoolProperty(
name="Keep Faces",
description="Keep or not the fill faces\n"
"Can depend on Remove Ngons state",
default=False
)
tri_faces: BoolProperty(
name="Triangulate Faces",
description="Triangulate the created fill faces\n"
"Sometimes can lead to unsatisfactory results",
default=False
)
initial_extrude: FloatVectorProperty(
name="Initial Extrude",
description="",
default=(0.0, 0.0, 0.1),
min=-20.0, max=20.0,
subtype='XYZ',
precision=3,
size=3
)
remove_ngons: BoolProperty(
name="Remove Ngons",
description="Keep or not the Ngon Faces\n"
"Note about limitations:\n"
"Sometimes the kept Faces could be Ngons\n"
"Removing the Ngons can lead to no geometry created",
default=True
)
offset: FloatProperty(
name="Wall Offset:",
description="Set the offset for the Solidify modifier",
default=0.0,
min=-1.0, max=1.0
)
only_rim: BoolProperty(
name="Rim Only",
description="Solidify Fill Rim only option",
default=False
)
@classmethod
def poll(cls, context):
ob = context.active_object
return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
def check_edge(self, context):
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
obj = bpy.context.object
me_check = obj.data
if len(me_check.edges) < 1:
return False
return True
@staticmethod
def ensure(bm):
if bm:
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
def solidify_mod(self, context, ob, wid, offset, only_rim):
try:
mods = ob.modifiers.new(
name="_Mesh_Solidify_Wall", type='SOLIDIFY'
)
mods.thickness = wid
mods.use_quality_normals = True
mods.offset = offset
mods.use_even_offset = True
mods.use_rim = True
mods.use_rim_only = only_rim
mods.show_on_cage = True
bpy.ops.object.modifier_apply(
modifier="_Mesh_Solidify_Wall"
)
except Exception as e:
error_handlers(self, e,
reports="Adding a Solidify Modifier failed")
pass
def draw(self, context):
layout = self.layout
box = layout.box()
box.label(text="Choose Method:", icon="SCRIPTWIN")
box.prop(self, "fill_type")
col = box.column(align=True)
if self.fill_type == 'EDGE_NET':
col.prop(self, "repeat_cleanup")
col.prop(self, "remove_ngons", toggle=True)
elif self.fill_type == 'SOLIDIFY':
col.prop(self, "offset", slider=True)
col.prop(self, "initial_extrude")
else:
col.prop(self, "remove_ngons", toggle=True)
col.prop(self, "tri_faces", toggle=True)
box = layout.box()
box.label(text="Settings:", icon="MOD_BUILD")
col = box.column(align=True)
col.prop(self, "wid")
if self.fill_type != 'SOLIDIFY':
col.prop(self, "depth")
col.prop(self, "connect_ends", toggle=True)
col.prop(self, "keep_faces", toggle=True)
else:
col.prop(self, "only_rim", toggle=True)
def execute(self, context):
if not self.check_edge(context):
self.report({'WARNING'},
"Operation Cancelled. Needs a Mesh with at least one edge")
return {'CANCELLED'}
wid = self.wid * 0.1
depth = self.depth * 0.1
offset = self.offset * 0.1
store_selection_mode = context.tool_settings.mesh_select_mode
# Note: the remove_doubles called after bmesh creation would make
# blender crash with certain meshes - keep it in mind for the future
bpy.ops.mesh.remove_doubles(threshold=0.003)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
ob = bpy.context.object
me = ob.data
bm = bmesh.from_edit_mesh(me)
bmesh.ops.delete(bm, geom=bm.faces, context=3)
self.ensure(bm)
context.tool_settings.mesh_select_mode = (False, True, False)
original_edges = [edge.index for edge in bm.edges]
original_verts = [vert.index for vert in bm.verts]
self.ensure(bm)
bpy.ops.mesh.select_all(action='DESELECT')
if self.fill_type == 'EDGE_NET':
for i in range(self.repeat_cleanup):
bmesh.ops.edgenet_prepare(bm, edges=bm.edges)
self.ensure(bm)
bmesh.ops.edgenet_fill(bm, edges=bm.edges, mat_nr=0, use_smooth=True, sides=0)
self.ensure(bm)
if self.remove_ngons:
ngons = [face for face in bm.faces if len(face.edges) > 4]
self.ensure(bm)
bmesh.ops.delete(bm, geom=ngons, context=5) # 5 - delete faces
del ngons
self.ensure(bm)
elif self.fill_type == 'SOLIDIFY':
for vert in bm.verts:
vert.normal_update()
self.ensure(bm)
bmesh.ops.extrude_edge_only(
bm, edges=bm.edges, use_select_history=False
)
self.ensure(bm)
verts_extrude = [vert for vert in bm.verts if vert.index in original_verts]
self.ensure(bm)
bmesh.ops.translate(
bm,
verts=verts_extrude,
vec=(self.initial_extrude)
)
self.ensure(bm)
del verts_extrude
self.ensure(bm)
for edge in bm.edges:
if edge.is_boundary:
edge.select = True
bm = bmesh.update_edit_mesh(ob.data, 1, 1)
bpy.ops.object.mode_set(mode='OBJECT')
self.solidify_mod(context, ob, wid, offset, self.only_rim)
bpy.ops.object.mode_set(mode='EDIT')
context.tool_settings.mesh_select_mode = store_selection_mode
return {'FINISHED'}
else:
bm.faces.new(bm.verts)
self.ensure(bm)
if self.tri_faces:
bmesh.ops.triangle_fill(
bm, use_beauty=True, use_dissolve=False, edges=bm.edges
)
self.ensure(bm)
if self.remove_ngons and self.fill_type != 'EDGE_NET':
ngons = [face for face in bm.faces if len(face.edges) > 4]
self.ensure(bm)
bmesh.ops.delete(bm, geom=ngons, context=5) # 5 - delete faces
del ngons
self.ensure(bm)
del_boundary = [edge for edge in bm.edges if edge.index not in original_edges]
self.ensure(bm)
del original_edges
self.ensure(bm)
if self.fill_type == 'EDGE_NET':
extrude_inner = bmesh.ops.inset_individual(
bm, faces=bm.faces, thickness=wid, depth=depth,
use_even_offset=True, use_interpolate=False,
use_relative_offset=False
)
else:
extrude_inner = bmesh.ops.inset_region(
bm, faces=bm.faces, faces_exclude=[], use_boundary=True,
use_even_offset=True, use_interpolate=False,
use_relative_offset=False, use_edge_rail=False,
thickness=wid, depth=depth, use_outset=False
)
self.ensure(bm)
del_faces = [faces for faces in bm.faces if faces not in extrude_inner["faces"]]
self.ensure(bm)
del extrude_inner
self.ensure(bm)
if not self.keep_faces:
bmesh.ops.delete(bm, geom=del_faces, context=5) # 5 delete faces
del del_faces
self.ensure(bm)
face_del = set()
for face in bm.faces:
for edge in del_boundary:
if isinstance(edge, bmesh.types.BMEdge):
if edge in face.edges:
face_del.add(face)
self.ensure(bm)
face_del = list(face_del)
self.ensure(bm)
del del_boundary
self.ensure(bm)
if not self.connect_ends:
bmesh.ops.delete(bm, geom=face_del, context=5)
self.ensure(bm)
del face_del
self.ensure(bm)
for edge in bm.edges:
if edge.is_boundary:
edge.select = True
bm = bmesh.update_edit_mesh(ob.data, 1, 1)
context.tool_settings.mesh_select_mode = store_selection_mode
return {'FINISHED'}
def register():
bpy.utils.register_class(MESH_OT_edges_floor_plan)
def unregister():
bpy.utils.unregister_class(MESH_OT_edges_floor_plan)
if __name__ == "__main__":
register()

View File

@ -1,341 +0,0 @@
# gpl author: Giuseppe De Marco [BlenderLab] inspired by NirenYang
bl_info = {
"name": "Set edges length",
"description": "Edges length",
"author": "Giuseppe De Marco [BlenderLab] inspired by NirenYang",
"version": (0, 1, 0),
"blender": (2, 71, 0),
"location": "Toolbar > Tools > Mesh Tools: set Length(Shit+Alt+E)",
"warning": "",
"wiki_url": "",
"category": "Mesh",
}
import bpy
import bmesh
from mathutils import Vector
from bpy.types import Operator
from bpy.props import (
FloatProperty,
EnumProperty,
)
# GLOBALS
edge_length_debug = False
_error_message = "Please select at least one edge to fill select history"
_error_message_2 = "Edges with shared vertices are not allowed. Please, use scale instead"
# Note : Refactor - removed all the operators apart from LengthSet
# and merged the other ones as options of length (lijenstina)
def get_edge_vector(edge):
verts = (edge.verts[0].co, edge.verts[1].co)
vector = verts[1] - verts[0]
return vector
def get_selected(bmesh_obj, geometry_type):
# geometry type should be edges, verts or faces
selected = []
for i in getattr(bmesh_obj, geometry_type):
if i.select:
selected.append(i)
return tuple(selected)
def get_center_vector(verts):
# verts = [Vector((x,y,z)), Vector((x,y,z))]
center_vector = Vector((((verts[1][0] + verts[0][0]) / 2.),
((verts[1][1] + verts[0][1]) / 2.),
((verts[1][2] + verts[0][2]) / 2.)))
return center_vector
class LengthSet(Operator):
bl_idname = "object.mesh_edge_length_set"
bl_label = "Set edge length"
bl_description = ("Change one selected edge length by a specified target,\n"
"existing length and different modes\n"
"Note: works only with Edges that not share a vertex")
bl_options = {'REGISTER', 'UNDO'}
old_length: FloatProperty(
name="Original length",
options={'HIDDEN'},
)
set_length_type: EnumProperty(
items=[
('manual', "Manual",
"Input manually the desired Target Length"),
('existing', "Existing Length",
"Use existing geometry Edges' characteristics"),
],
name="Set Type of Input",
)
target_length: FloatProperty(
name="Target Length",
description="Input a value for an Edges Length target",
default=1.00,
unit='LENGTH',
precision=5
)
existing_length: EnumProperty(
items=[
('min', "Shortest",
"Set all to shortest Edge of selection"),
('max', "Longest",
"Set all to the longest Edge of selection"),
('average', "Average",
"Set all to the average Edge length of selection"),
('active', "Active",
"Set all to the active Edge's one\n"
"Needs a selection to be done in Edge Select mode"),
],
name="Existing length"
)
mode: EnumProperty(
items=[
('fixed', "Fixed", "Fixed"),
('increment', "Increment", "Increment"),
('decrement', "Decrement", "Decrement"),
],
name="Mode"
)
behaviour: EnumProperty(
items=[
('proportional', "Proportional",
"Move vertex locations proportionally to the center of the Edge"),
('clockwise', "Clockwise",
"Compute the Edges' vertex locations in a clockwise fashion"),
('unclockwise', "Counterclockwise",
"Compute the Edges' vertex locations in a counterclockwise fashion"),
],
name="Resize behavior"
)
originary_edge_length_dict = {}
edge_lengths = []
selected_edges = ()
@classmethod
def poll(cls, context):
return (context.edit_object and context.object.type == 'MESH')
def check(self, context):
return True
def draw(self, context):
layout = self.layout
layout.label(text="Original Active length is: {:.3f}".format(self.old_length))
layout.label(text="Input Mode:")
layout.prop(self, "set_length_type", expand=True)
if self.set_length_type == 'manual':
layout.prop(self, "target_length")
else:
layout.prop(self, "existing_length", text="")
layout.label(text="Mode:")
layout.prop(self, "mode", text="")
layout.label(text="Resize Behavior:")
layout.prop(self, "behaviour", text="")
def get_existing_edge_length(self, bm):
if self.existing_length != "active":
if self.existing_length == "min":
return min(self.edge_lengths)
if self.existing_length == "max":
return max(self.edge_lengths)
elif self.existing_length == "average":
return sum(self.edge_lengths) / float(len(self.selected_edges))
else:
bm.edges.ensure_lookup_table()
active_edge_length = None
for elem in reversed(bm.select_history):
if isinstance(elem, bmesh.types.BMEdge):
active_edge_length = elem.calc_length()
break
return active_edge_length
return 0.0
def invoke(self, context, event):
wm = context.window_managerlength
obj = context.edit_object
bm = bmesh.from_edit_mesh(obj.data)
bpy.ops.mesh.select_mode(type="EDGE")
self.selected_edges = get_selected(bm, 'edges')
if self.selected_edges:
vertex_set = []
for edge in self.selected_edges:
vector = get_edge_vector(edge)
if edge.verts[0].index not in vertex_set:
vertex_set.append(edge.verts[0].index)
else:
self.report({'ERROR_INVALID_INPUT'}, _error_message_2)
return {'CANCELLED'}
if edge.verts[1].index not in vertex_set:
vertex_set.append(edge.verts[1].index)
else:
self.report({'ERROR_INVALID_INPUT'}, _error_message_2)
return {'CANCELLED'}
# warning, it's a constant !
verts_index = ''.join((str(edge.verts[0].index), str(edge.verts[1].index)))
self.originary_edge_length_dict[verts_index] = vector
self.edge_lengths.append(vector.length)
self.old_length = vector.length
else:
self.report({'ERROR'}, _error_message)
return {'CANCELLED'}
if edge_length_debug:
self.report({'INFO'}, str(self.originary_edge_length_dict))
if bpy.context.scene.unit_settings.system == 'IMPERIAL':
# imperial to metric conversion
vector.length = (0.9144 * vector.length) / 3
self.target_length = vector.length
return wm.invoke_props_dialog(self)
def execute(self, context):
bpy.ops.mesh.select_mode(type="EDGE")
self.context = context
obj = context.edit_object
bm = bmesh.from_edit_mesh(obj.data)
self.selected_edges = get_selected(bm, 'edges')
if not self.selected_edges:
self.report({'ERROR'}, _error_message)
return {'CANCELLED'}
for edge in self.selected_edges:
vector = get_edge_vector(edge)
# what we should see in original length dialog field
self.old_length = vector.length
if self.set_length_type == 'manual':
vector.length = abs(self.target_length)
else:
get_lengths = self.get_existing_edge_length(bm)
# check for edit mode
if not get_lengths:
self.report({'WARNING'},
"Operation Cancelled. "
"Active Edge could not be determined (needs selection in Edit Mode)")
return {'CANCELLED'}
vector.length = get_lengths
if vector.length == 0.0:
self.report({'ERROR'}, "Operation cancelled. Target length is set to zero")
return {'CANCELLED'}
center_vector = get_center_vector((edge.verts[0].co, edge.verts[1].co))
verts_index = ''.join((str(edge.verts[0].index), str(edge.verts[1].index)))
if edge_length_debug:
self.report({'INFO'},
' - '.join(('vector ' + str(vector),
'originary_vector ' +
str(self.originary_edge_length_dict[verts_index])
)))
verts = (edge.verts[0].co, edge.verts[1].co)
if edge_length_debug:
self.report({'INFO'},
'\n edge.verts[0].co ' + str(verts[0]) +
'\n edge.verts[1].co ' + str(verts[1]) +
'\n vector.length' + str(vector.length))
# the clockwise direction have v1 -> v0, unclockwise v0 -> v1
if self.target_length >= 0:
if self.behaviour == 'proportional':
edge.verts[1].co = center_vector + vector / 2
edge.verts[0].co = center_vector - vector / 2
if self.mode == 'decrement':
edge.verts[0].co = (center_vector + vector / 2) - \
(self.originary_edge_length_dict[verts_index] / 2)
edge.verts[1].co = (center_vector - vector / 2) + \
(self.originary_edge_length_dict[verts_index] / 2)
elif self.mode == 'increment':
edge.verts[1].co = (center_vector + vector / 2) + \
self.originary_edge_length_dict[verts_index] / 2
edge.verts[0].co = (center_vector - vector / 2) - \
self.originary_edge_length_dict[verts_index] / 2
elif self.behaviour == 'unclockwise':
if self.mode == 'increment':
edge.verts[1].co = \
verts[0] + (self.originary_edge_length_dict[verts_index] + vector)
elif self.mode == 'decrement':
edge.verts[0].co = \
verts[1] - (self.originary_edge_length_dict[verts_index] - vector)
else:
edge.verts[1].co = verts[0] + vector
else:
# clockwise
if self.mode == 'increment':
edge.verts[0].co = \
verts[1] - (self.originary_edge_length_dict[verts_index] + vector)
elif self.mode == 'decrement':
edge.verts[1].co = \
verts[0] + (self.originary_edge_length_dict[verts_index] - vector)
else:
edge.verts[0].co = verts[1] - vector
if bpy.context.scene.unit_settings.system == 'IMPERIAL':
"""
# yards to metric conversion
vector.length = ( 3. * vector.length ) / 0.9144
# metric to yards conversion
vector.length = ( 0.9144 * vector.length ) / 3.
"""
for mvert in edge.verts:
# school time: 0.9144 : 3 = X : mvert
mvert.co = (0.9144 * mvert.co) / 3
if edge_length_debug:
self.report({'INFO'},
'\n edge.verts[0].co' + str(verts[0]) +
'\n edge.verts[1].co' + str(verts[1]) +
'\n vector' + str(vector) + '\n v1 > v0:' + str((verts[1] >= verts[0]))
)
bmesh.update_edit_mesh(obj.data, True)
return {'FINISHED'}
def register():
bpy.utils.register_class(LengthSet)
def unregister():
bpy.utils.unregister_class(LengthSet)
if __name__ == "__main__":
register()

File diff suppressed because it is too large Load Diff

View File

@ -1,378 +0,0 @@
# ##### 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 #####
# Contact for more information about the Addon:
# Email: germano.costa@ig.com.br
# Twitter: wii_mano @mano_wii
bl_info = {
"name": "Extrude and Reshape",
"author": "Germano Cavalcante",
"version": (0, 8, 1),
"blender": (2, 76, 5),
"location": "View3D > TOOLS > Tools > Mesh Tools > Add: > Extrude Menu (Alt + E)",
"description": "Extrude face and merge edge intersections "
"between the mesh and the new edges",
"wiki_url": "http://blenderartists.org/forum/"
"showthread.php?376618-Addon-Push-Pull-Face",
"category": "Mesh"}
import bpy
import bmesh
from mathutils.geometry import intersect_line_line
from bpy.types import Operator
class BVHco():
i = 0
c1x = 0.0
c1y = 0.0
c1z = 0.0
c2x = 0.0
c2y = 0.0
c2z = 0.0
def edges_BVH_overlap(bm, edges, epsilon=0.0001):
bco = set()
for e in edges:
bvh = BVHco()
bvh.i = e.index
b1 = e.verts[0]
b2 = e.verts[1]
co1 = b1.co.x
co2 = b2.co.x
if co1 <= co2:
bvh.c1x = co1 - epsilon
bvh.c2x = co2 + epsilon
else:
bvh.c1x = co2 - epsilon
bvh.c2x = co1 + epsilon
co1 = b1.co.y
co2 = b2.co.y
if co1 <= co2:
bvh.c1y = co1 - epsilon
bvh.c2y = co2 + epsilon
else:
bvh.c1y = co2 - epsilon
bvh.c2y = co1 + epsilon
co1 = b1.co.z
co2 = b2.co.z
if co1 <= co2:
bvh.c1z = co1 - epsilon
bvh.c2z = co2 + epsilon
else:
bvh.c1z = co2 - epsilon
bvh.c2z = co1 + epsilon
bco.add(bvh)
del edges
overlap = {}
oget = overlap.get
for e1 in bm.edges:
by = bz = True
a1 = e1.verts[0]
a2 = e1.verts[1]
c1x = a1.co.x
c2x = a2.co.x
if c1x > c2x:
tm = c1x
c1x = c2x
c2x = tm
for bvh in bco:
if c1x <= bvh.c2x and c2x >= bvh.c1x:
if by:
by = False
c1y = a1.co.y
c2y = a2.co.y
if c1y > c2y:
tm = c1y
c1y = c2y
c2y = tm
if c1y <= bvh.c2y and c2y >= bvh.c1y:
if bz:
bz = False
c1z = a1.co.z
c2z = a2.co.z
if c1z > c2z:
tm = c1z
c1z = c2z
c2z = tm
if c1z <= bvh.c2z and c2z >= bvh.c1z:
e2 = bm.edges[bvh.i]
if e1 != e2:
overlap[e1] = oget(e1, set()).union({e2})
return overlap
def intersect_edges_edges(overlap, precision=4):
epsilon = .1**precision
fpre_min = -epsilon
fpre_max = 1 + epsilon
splits = {}
sp_get = splits.get
new_edges1 = set()
new_edges2 = set()
targetmap = {}
for edg1 in overlap:
# print("***", ed1.index, "***")
for edg2 in overlap[edg1]:
a1 = edg1.verts[0]
a2 = edg1.verts[1]
b1 = edg2.verts[0]
b2 = edg2.verts[1]
# test if are linked
if a1 in {b1, b2} or a2 in {b1, b2}:
# print('linked')
continue
aco1, aco2 = a1.co, a2.co
bco1, bco2 = b1.co, b2.co
tp = intersect_line_line(aco1, aco2, bco1, bco2)
if tp:
p1, p2 = tp
if (p1 - p2).to_tuple(precision) == (0, 0, 0):
v = aco2 - aco1
f = p1 - aco1
x, y, z = abs(v.x), abs(v.y), abs(v.z)
max1 = 0 if x >= y and x >= z else\
1 if y >= x and y >= z else 2
fac1 = f[max1] / v[max1]
v = bco2 - bco1
f = p2 - bco1
x, y, z = abs(v.x), abs(v.y), abs(v.z)
max2 = 0 if x >= y and x >= z else\
1 if y >= x and y >= z else 2
fac2 = f[max2] / v[max2]
if fpre_min <= fac1 <= fpre_max:
# print(edg1.index, 'can intersect', edg2.index)
ed1 = edg1
elif edg1 in splits:
for ed1 in splits[edg1]:
a1 = ed1.verts[0]
a2 = ed1.verts[1]
vco1 = a1.co
vco2 = a2.co
v = vco2 - vco1
f = p1 - vco1
fac1 = f[max1] / v[max1]
if fpre_min <= fac1 <= fpre_max:
# print(e.index, 'can intersect', edg2.index)
break
else:
# print(edg1.index, 'really does not intersect', edg2.index)
continue
else:
# print(edg1.index, 'not intersect', edg2.index)
continue
if fpre_min <= fac2 <= fpre_max:
# print(ed1.index, 'actually intersect', edg2.index)
ed2 = edg2
elif edg2 in splits:
for ed2 in splits[edg2]:
b1 = ed2.verts[0]
b2 = ed2.verts[1]
vco1 = b1.co
vco2 = b2.co
v = vco2 - vco1
f = p2 - vco1
fac2 = f[max2] / v[max2]
if fpre_min <= fac2 <= fpre_max:
# print(ed1.index, 'actually intersect', e.index)
break
else:
# print(ed1.index, 'really does not intersect', ed2.index)
continue
else:
# print(ed1.index, 'not intersect', edg2.index)
continue
new_edges1.add(ed1)
new_edges2.add(ed2)
if abs(fac1) <= epsilon:
nv1 = a1
elif fac1 + epsilon >= 1:
nv1 = a2
else:
ne1, nv1 = bmesh.utils.edge_split(ed1, a1, fac1)
new_edges1.add(ne1)
splits[edg1] = sp_get(edg1, set()).union({ne1})
if abs(fac2) <= epsilon:
nv2 = b1
elif fac2 + epsilon >= 1:
nv2 = b2
else:
ne2, nv2 = bmesh.utils.edge_split(ed2, b1, fac2)
new_edges2.add(ne2)
splits[edg2] = sp_get(edg2, set()).union({ne2})
if nv1 != nv2: # necessary?
targetmap[nv1] = nv2
return new_edges1, new_edges2, targetmap
class Extrude_and_Reshape(Operator):
bl_idname = "mesh.extrude_reshape"
bl_label = "Extrude and Reshape"
bl_description = "Push and pull face entities to sculpt 3d models"
bl_options = {'REGISTER', 'GRAB_CURSOR', 'BLOCKING'}
@classmethod
def poll(cls, context):
return context.mode != 'EDIT_MESH'
def modal(self, context, event):
if self.confirm:
sface = self.bm.faces.active
if not sface:
for face in self.bm.faces:
if face.select is True:
sface = face
break
else:
return {'FINISHED'}
# edges to intersect
edges = set()
[[edges.add(ed) for ed in v.link_edges] for v in sface.verts]
overlap = edges_BVH_overlap(self.bm, edges, epsilon=0.0001)
overlap = {k: v for k, v in overlap.items() if k not in edges} # remove repetition
"""
print([e.index for e in edges])
for a, b in overlap.items():
print(a.index, [e.index for e in b])
"""
new_edges1, new_edges2, targetmap = intersect_edges_edges(overlap)
pos_weld = set()
for e in new_edges1:
v1, v2 = e.verts
if v1 in targetmap and v2 in targetmap:
pos_weld.add((targetmap[v1], targetmap[v2]))
if targetmap:
bmesh.ops.weld_verts(self.bm, targetmap=targetmap)
"""
print([e.is_valid for e in new_edges1])
print([e.is_valid for e in new_edges2])
sp_faces1 = set()
"""
for e in pos_weld:
v1, v2 = e
lf1 = set(v1.link_faces)
lf2 = set(v2.link_faces)
rlfe = lf1.intersection(lf2)
for f in rlfe:
try:
nf = bmesh.utils.face_split(f, v1, v2)
# sp_faces1.update({f, nf[0]})
except:
pass
# sp_faces2 = set()
for e in new_edges2:
lfe = set(e.link_faces)
v1, v2 = e.verts
lf1 = set(v1.link_faces)
lf2 = set(v2.link_faces)
rlfe = lf1.intersection(lf2)
for f in rlfe.difference(lfe):
nf = bmesh.utils.face_split(f, v1, v2)
# sp_faces2.update({f, nf[0]})
bmesh.update_edit_mesh(self.mesh, loop_triangles=True, destructive=True)
return {'FINISHED'}
if self.cancel:
return {'FINISHED'}
self.cancel = event.type in {'ESC', 'NDOF_BUTTON_ESC'}
self.confirm = event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}
return {'PASS_THROUGH'}
def execute(self, context):
self.mesh = context.object.data
self.bm = bmesh.from_edit_mesh(self.mesh)
try:
selection = self.bm.select_history[-1]
except:
for face in self.bm.faces:
if face.select is True:
selection = face
break
else:
return {'FINISHED'}
if not isinstance(selection, bmesh.types.BMFace):
bpy.ops.mesh.extrude_region_move('INVOKE_DEFAULT')
return {'FINISHED'}
else:
face = selection
# face.select = False
bpy.ops.mesh.select_all(action='DESELECT')
geom = []
for edge in face.edges:
if abs(edge.calc_face_angle(0) - 1.5707963267948966) < 0.01: # self.angle_tolerance:
geom.append(edge)
ret_dict = bmesh.ops.extrude_discrete_faces(self.bm, faces=[face])
for face in ret_dict['faces']:
self.bm.faces.active = face
face.select = True
sface = face
dfaces = bmesh.ops.dissolve_edges(
self.bm, edges=geom, use_verts=True, use_face_split=False
)
bmesh.update_edit_mesh(self.mesh, loop_triangles=True, destructive=True)
bpy.ops.transform.translate(
'INVOKE_DEFAULT', constraint_axis=(False, False, True),
orient_type='NORMAL', release_confirm=True
)
context.window_manager.modal_handler_add(self)
self.cancel = False
self.confirm = False
return {'RUNNING_MODAL'}
def operator_draw(self, context):
layout = self.layout
col = layout.column(align=True)
col.operator("mesh.extrude_reshape", text="Extrude and Reshape")
def register():
bpy.utils.register_class(Extrude_and_Reshape)
bpy.types.VIEW3D_MT_edit_mesh_extrude.append(operator_draw)
def unregister():
bpy.types.VIEW3D_MT_edit_mesh_extrude.remove(operator_draw)
bpy.utils.unregister_class(Extrude_and_Reshape)
if __name__ == "__main__":
register()

View File

@ -1,112 +0,0 @@
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Fast Loop",
"description": "Add loops fast",
"author": "Andy Davies (metalliandy)",
"version": (0, 1, 7),
"blender": (2, 56, 0),
"location": "Tool Shelf",
"warning": "",
"wiki_url": "",
"category": "Mesh"
}
"""
About this script:-
This script enables the fast creation of multiple loops on a mesh
Usage:-
1)Click the FastLoop button on the Tool Shelf to activate the tool
2)Hover over the mesh in the general area where you would like a loop to be added
(shown by a highlight on the mesh)
3)Click once to confirm the loop placement
4)place the loop and then slide to fine tune its position
5)Repeat 1-4 if needed
6)Press Esc. twice to exit the tool
Related Links:-
http://blenderartists.org/forum/showthread.php?t=206989
http://www.metalliandy.com
Thanks to:-
Bartius Crouch (Crouch) - http://sites.google.com/site/bartiuscrouch/
Dealga McArdle (zeffii) - http://www.digitalaphasia.com
Version history:-
v0.16 - Amended script for compatibility with recent API changes
v0.15 - Amended script meta information and button rendering code for
compatibility with recent API changes
v0.14 - Modal operator
v0.13 - Initial revision
"""
import bpy
from bpy.types import Operator
from bpy.props import BoolProperty
class OBJECT_OT_FastLoop(Operator):
bl_idname = "object_ot.fastloop"
bl_label = "FastLoop"
bl_description = ("Create multiple edge loops in succession\n"
"Runs modal until ESC is pressed twice")
active: BoolProperty(
name="active",
default=False
)
@classmethod
def poll(cls, context):
return bpy.ops.mesh.loopcut_slide.poll()
def modal(self, context, event):
if event.type == 'ESC':
context.area.header_text_set(None)
return {'CANCELLED'}
elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
self.active = False
if not self.active:
self.active = True
bpy.ops.mesh.loopcut_slide('INVOKE_DEFAULT')
context.area.header_text_set("Press ESC twice to stop FastLoop")
return {'RUNNING_MODAL'}
def invoke(self, context, event):
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def register():
bpy.utils.register_module(__name__)
pass
def unregister():
bpy.utils.unregister_module(__name__)
pass
if __name__ == "__main__":
register()

View File

@ -1,412 +0,0 @@
# -*- coding: utf-8 -*-
# ##### END 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "FilletPlus",
"author": "Gert De Roost - original by zmj100",
"version": (0, 4, 3),
"blender": (2, 61, 0),
"location": "View3D > Tool Shelf",
"description": "",
"warning": "",
"wiki_url": "",
"category": "Mesh"}
import bpy
from bpy.props import (
FloatProperty,
IntProperty,
BoolProperty,
)
from bpy.types import Operator
import bmesh
from mathutils import Matrix
from math import (
cos, pi, sin,
degrees, tan,
)
def list_clear_(l):
if l:
del l[:]
return l
def get_adj_v_(list_):
tmp = {}
for i in list_:
try:
tmp[i[0]].append(i[1])
except KeyError:
tmp[i[0]] = [i[1]]
try:
tmp[i[1]].append(i[0])
except KeyError:
tmp[i[1]] = [i[0]]
return tmp
class f_buf():
# one of the angles was not 0 or 180
check = False
def fillets(list_0, startv, vertlist, face, adj, n, out, flip, radius):
try:
dict_0 = get_adj_v_(list_0)
list_1 = [[dict_0[i][0], i, dict_0[i][1]] for i in dict_0 if (len(dict_0[i]) == 2)][0]
list_3 = []
for elem in list_1:
list_3.append(bm.verts[elem])
list_2 = []
p_ = list_3[1]
p = (list_3[1].co).copy()
p1 = (list_3[0].co).copy()
p2 = (list_3[2].co).copy()
vec1 = p - p1
vec2 = p - p2
ang = vec1.angle(vec2, any)
check_angle = round(degrees(ang))
if check_angle == 180 or check_angle == 0.0:
return False
else:
f_buf.check = True
opp = adj
if radius is False:
h = adj * (1 / cos(ang * 0.5))
adj_ = adj
elif radius is True:
h = opp / sin(ang * 0.5)
adj_ = opp / tan(ang * 0.5)
p3 = p - (vec1.normalized() * adj_)
p4 = p - (vec2.normalized() * adj_)
rp = p - ((p - ((p3 + p4) * 0.5)).normalized() * h)
vec3 = rp - p3
vec4 = rp - p4
axis = vec1.cross(vec2)
if out is False:
if flip is False:
rot_ang = vec3.angle(vec4)
elif flip is True:
rot_ang = vec1.angle(vec2)
elif out is True:
rot_ang = (2 * pi) - vec1.angle(vec2)
for j in range(n + 1):
new_angle = rot_ang * j / n
mtrx = Matrix.Rotation(new_angle, 3, axis)
if out is False:
if flip is False:
tmp = p4 - rp
tmp1 = mtrx * tmp
tmp2 = tmp1 + rp
elif flip is True:
p3 = p - (vec1.normalized() * opp)
tmp = p3 - p
tmp1 = mtrx * tmp
tmp2 = tmp1 + p
elif out is True:
p4 = p - (vec2.normalized() * opp)
tmp = p4 - p
tmp1 = mtrx * tmp
tmp2 = tmp1 + p
v = bm.verts.new(tmp2)
list_2.append(v)
if flip is True:
list_3[1:2] = list_2
else:
list_2.reverse()
list_3[1:2] = list_2
list_clear_(list_2)
n1 = len(list_3)
for t in range(n1 - 1):
bm.edges.new([list_3[t], list_3[(t + 1) % n1]])
v = bm.verts.new(p)
bm.edges.new([v, p_])
bm.edges.ensure_lookup_table()
if face is not None:
for l in face.loops:
if l.vert == list_3[0]:
startl = l
break
vertlist2 = []
if startl.link_loop_next.vert == startv:
l = startl.link_loop_prev
while len(vertlist) > 0:
vertlist2.insert(0, l.vert)
vertlist.pop(vertlist.index(l.vert))
l = l.link_loop_prev
else:
l = startl.link_loop_next
while len(vertlist) > 0:
vertlist2.insert(0, l.vert)
vertlist.pop(vertlist.index(l.vert))
l = l.link_loop_next
for v in list_3:
vertlist2.append(v)
bm.faces.new(vertlist2)
if startv.is_valid:
bm.verts.remove(startv)
else:
print("\n[Function fillets Error]\n"
"Starting vertex (startv var) couldn't be removed\n")
return False
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
list_3[1].select = 1
list_3[-2].select = 1
bm.edges.get([list_3[0], list_3[1]]).select = 1
bm.edges.get([list_3[-1], list_3[-2]]).select = 1
bm.verts.index_update()
bm.edges.index_update()
bm.faces.index_update()
me.update(calc_edges=True, calc_loop_triangles=True)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
except Exception as e:
print("\n[Function fillets Error]\n{}\n".format(e))
return False
def do_filletplus(self, pair):
is_finished = True
try:
startv = None
global inaction
global flip
list_0 = [list([e.verts[0].index, e.verts[1].index]) for e in pair]
vertset = set([])
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
vertset.add(bm.verts[list_0[0][0]])
vertset.add(bm.verts[list_0[0][1]])
vertset.add(bm.verts[list_0[1][0]])
vertset.add(bm.verts[list_0[1][1]])
v1, v2, v3 = vertset
if len(list_0) != 2:
self.report({'WARNING'}, "Two adjacent edges must be selected")
is_finished = False
else:
inaction = 1
vertlist = []
found = 0
for f in v1.link_faces:
if v2 in f.verts and v3 in f.verts:
found = 1
if not found:
for v in [v1, v2, v3]:
if v.index in list_0[0] and v.index in list_0[1]:
startv = v
face = None
else:
for f in v1.link_faces:
if v2 in f.verts and v3 in f.verts:
for v in f.verts:
if not(v in vertset):
vertlist.append(v)
if (v in vertset and v.link_loops[0].link_loop_prev.vert in vertset and
v.link_loops[0].link_loop_next.vert in vertset):
startv = v
face = f
if out is True:
flip = False
if startv:
fills = fillets(list_0, startv, vertlist, face, adj, n, out, flip, radius)
if not fills:
is_finished = False
else:
is_finished = False
except Exception as e:
print("\n[Function do_filletplus Error]\n{}\n".format(e))
is_finished = False
return is_finished
def check_is_not_coplanar(bm_data):
from mathutils import Vector
check = False
angles, norm_angle = 0, 0
z_vec = Vector((0, 0, 1))
try:
bm_data.faces.ensure_lookup_table()
for f in bm_data.faces:
norm_angle = f.normal.angle(z_vec)
if angles == 0:
angles = norm_angle
if angles != norm_angle:
check = True
break
except Exception as e:
print("\n[Function check_is_not_coplanar Error]\n{}\n".format(e))
check = True
return check
# Operator
class MESH_OT_fillet_plus(Operator):
bl_idname = "mesh.fillet_plus"
bl_label = "Fillet Plus"
bl_description = ("Fillet adjoining edges\n"
"Note: Works on a mesh whose all faces share the same normal")
bl_options = {"REGISTER", "UNDO"}
adj: FloatProperty(
name="",
description="Size of the filleted corners",
default=0.1,
min=0.00001, max=100.0,
step=1,
precision=3
)
n: IntProperty(
name="",
description="Subdivision of the filleted corners",
default=3,
min=1, max=50,
step=1
)
out: BoolProperty(
name="Outside",
description="Fillet towards outside",
default=False
)
flip: BoolProperty(
name="Flip",
description="Flip the direction of the Fillet\n"
"Only available if Outside option is not active",
default=False
)
radius: BoolProperty(
name="Radius",
description="Use radius for the size of the filleted corners",
default=False
)
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')
def draw(self, context):
layout = self.layout
if f_buf.check is False:
layout.label(text="Angle is equal to 0 or 180", icon="INFO")
layout.label(text="Can not fillet", icon="BLANK1")
else:
layout.prop(self, "radius")
if self.radius is True:
layout.label(text="Radius:")
elif self.radius is False:
layout.label(text="Distance:")
layout.prop(self, "adj")
layout.label(text="Number of sides:")
layout.prop(self, "n")
if self.n > 1:
row = layout.row(align=False)
row.prop(self, "out")
if self.out is False:
row.prop(self, "flip")
def execute(self, context):
global inaction
global bm, me, adj, n, out, flip, radius
adj = self.adj
n = self.n
out = self.out
flip = self.flip
radius = self.radius
inaction = 0
f_buf.check = False
ob_act = context.active_object
try:
me = ob_act.data
bm = bmesh.from_edit_mesh(me)
warn_obj = bool(check_is_not_coplanar(bm))
if warn_obj is False:
tempset = set([])
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
for v in bm.verts:
if v.select and v.is_boundary:
tempset.add(v)
for v in tempset:
edgeset = set([])
for e in v.link_edges:
if e.select and e.is_boundary:
edgeset.add(e)
if len(edgeset) == 2:
is_finished = do_filletplus(self, edgeset)
if not is_finished:
break
if inaction == 1:
bpy.ops.mesh.select_all(action="DESELECT")
for v in bm.verts:
if len(v.link_edges) == 0:
bm.verts.remove(v)
bpy.ops.object.editmode_toggle()
bpy.ops.object.editmode_toggle()
else:
self.report({'WARNING'}, "Filletplus operation could not be performed")
return {'CANCELLED'}
else:
self.report({'WARNING'}, "Mesh is not a coplanar surface. Operation cancelled")
return {'CANCELLED'}
except:
self.report({'WARNING'}, "Filletplus operation could not be performed")
return {'CANCELLED'}
return {'FINISHED'}

View File

@ -1,244 +0,0 @@
# gpl authors: lijenstina, meta-androcto
# Note: this script contains the Help Operator used by the various functions
# Usage: add a key string to the dictionary in this file with the list of strings to pass to labels
# and call the operator from the add-on UI draw function by passing the help_ids parameter
# If the size of the pop-up if needed, define popup_size in the call by using variables
# Example (with using the variable props):
# props = layout.row("mesh.extra_tools_help")
# props.help_ids = "default"
# props.popup_size = 400
import bpy
from bpy.types import Operator
from bpy.props import (
StringProperty,
IntProperty,
)
class MESH_OT_extra_tools_help(Operator):
bl_idname = "mesh.extra_tools_help"
bl_label = ""
bl_description = "Tool Help - click to read some basic information"
bl_options = {'REGISTER'}
help_ids: StringProperty(
name="ID of the Operator to display",
options={'HIDDEN'},
default="default"
)
popup_size: IntProperty(
name="Size of the Help Pop-up Menu",
default=350,
min=100,
max=600,
)
def draw(self, context):
layout = self.layout
pick_help = help_custom_draw(self.help_ids)
for line_text in pick_help:
layout.label(line_text)
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_popup(self, width=self.popup_size)
def help_custom_draw(identifier="default"):
# A table of lists containing the help text under an index key that is the script name
# If several returns are needed per file, add some suffix after the script name
# and call them separately
# In case nothing is passed from the UI call, the returned list is default
# If undefined one is passed, it will return a warning message
help_text = {
"default": [
"This is a placeholder text",
"Please fill up the entries in the " + __name__ + " script",
],
"random_vertices": [
"To use:",
"Make a selection or selection of Vertices",
"Randomize displaced positions",
"Note:",
"There is an option to use Vertex Weights for displacement",
"Prior to use, don't forget to assign after updating the Group Weight",
],
"mesh_vertex_chamfer": [
"To use:",
"Make a selection or selection of vertices",
"Result is a triangle Chamfer, works on a single vertex",
"Note:",
"The difference to the vertex Bevel is that original geometry",
"(selected vertices) can optionally be kept and displaced",
"Limitation:",
"In some cases, may need to press F to fill the result",
],
"mesh_filletplus": [
"To use:",
"Select two adjacent edges and press Fillet button",
"Limitation:",
"Works on a mesh with all faces sharing the same normal",
"(Flat Surface - faces have the same direction)",
"Planes with already round corners can produce unsatisfactory results",
"Only boundary edges will be evaluated",
],
"mesh_offset_edges": [
"To use:",
"Make a selection or selection of Edges",
"Extrude, rotate extrusions and more",
"Limitation:",
"Operates only on separate Edge loops selections",
"(i.e. Edge loops that are not connected by a selected edge)",
],
"mesh_edge_roundifier": [
"To use:",
"Select a single or multiple Edges",
"Make Arcs with various parameters",
"Reference, Rotation, Scaling, Connection and Offset",
"Note:",
"The Mode - Reset button restores the default values",
],
"mesh_edges_length": [
"To use:",
"Select a single or multiple Edges",
"Change length with various parameters",
"Limitation:",
"Does not operate on edges that share a vertex",
"If the selection wasn't done in Edge Selection mode,",
"the option Active will not work (due to Blender's limitation)",
],
"mesh_edges_floor_plan": [
"To use:",
"Starting edges will be flat extruded forming faces strips",
"on the inside. Similar to using Face fill inset select outer",
"Methods:",
"Edge Net: Fills the edge grid with faces then Inset",
"Single Face: Single Face fill (all Edges) then Inset",
"Solidify: Extrude along defined axis, apply a Solidify modifier",
"Note:",
"Grid Fill and Single Face sometimes need tweaking with the options",
"Limitation:",
"Depending on the input geometry, Keep Ngons sometimes needs to be",
"enabled to produce any results",
"Edge Net and Single Face depend on bmesh face fill and inset",
"that sometimes can fail to produce good results",
"Avoid using Single Face Method on Edges that define a Volume - like Suzanne",
"Solidify method works best for flat surfaces and complex geometry",
],
"mesh_mextrude_plus": [
"To use:",
"Make a selection of Faces",
"Extrude with Rotation, Scaling, Variation,",
"Randomization and Offset parameters",
"Limitation:",
"Works only with selections that enclose Faces",
"(i.e. all Edges or Vertices of a Face selected)",
],
"mesh_extrude_and_reshape": [
"To use:",
"Extrude Face and merge Edge intersections,",
"between the mesh and the new Edges",
"Note:",
"If selected Vertices don't form Face they will be",
"still extruded in the same direction",
"Limitation:",
"Works only with the last selected face",
"(or all Edges or Vertices of a Face selected)",
],
"face_inset_fillet": [
"To use:",
"Select one or multiple faces and inset",
"Inset square, circle or outside",
"Note:",
"Radius: use remove doubles to tidy joins",
"Out: select and use normals flip before extruding",
"Limitation:",
"Using the Out option, sometimes can lead to unsatisfactory results",
],
"mesh_cut_faces": [
"To use:",
"Make a selection or selection of Faces",
"Some Functions work on a plane only",
"Limitation:",
"The selection must include at least two Faces with adjacent edges",
"(Selections not sharing edges will not work)",
],
"split_solidify": [
"To use:",
"Make a selection or selection of Faces",
"Split Faces and Extrude results",
"Similar to a shatter/explode effect",
],
"mesh_fastloop": [
"To use:",
"Activate the tool and hover over the mesh in the general area",
"for the loop and left click once to confirm the loop placement",
"Slide using the mouse to fine tune its position, left click to confirm",
"Repeat the operations if needed for new loops",
"Press Esc. twice to exit the tool",
"Limitation:",
"The tool has the same limitations as Loop Cut and Slide",
"In the Operator Panel, only the last loop can be tweaked",
],
"mesh_pen_tool": [
"To use:",
"Press Ctrl + D key or click Draw button",
"To draw along x use SHIFT + MOUSEMOVE",
"To draw along y use ALT + MOUSEMOVE",
"Press Ctrl to toggle Extrude at Cursor tool",
"Right click to finish drawing or",
"Press Esc to cancel",
],
"pkhg_faces": [
"To use:",
"Needs a Face Selection in Edit Mode",
"Select an option from Face Types drop down list",
"Extrude, rotate extrusions and more",
"Toggle Edit Mode after use",
"Note:",
"After using the operator, normals could need repair,",
"or Removing Doubles",
],
"vertex_align": [
"To use:",
"Select vertices that you want to align and click Align button",
"Options include aligning to defined Custom coordinates or",
"Stored vertex - (a single selected one with Store Selected Vertex)",
"Note:",
"Use Stored Coordinates - allows to save a set of coordinates",
"as a starting point that can be tweaked on during operation",
],
"mesh_check": [
"To use:",
"Tris and Ngons will select Faces by corensponding type",
"Display faces will color the faces depending on the",
"defined Colors, Edges' width and Face Opacity",
"Note:",
"The Faces' type count is already included elsewhere:",
"In the Properties Editor > Data > Face / Info Select Panel",
],
}
if identifier in help_text:
return help_text[identifier]
return ["ERROR:", "Help Operator", "Undefined call to the Dictionary"]
# register
def register():
bpy.utils.register_class(MESH_OT_extra_tools_help)
def unregister():
bpy.utils.unregister_class(MESH_OT_extra_tools_help)
if __name__ == "__main__":
register()

View File

@ -1,370 +0,0 @@
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# Repeats extrusion + rotation + scale for one or more faces
# Original code by liero
# Update by Jimmy Hazevoet 03/2017 for Blender 2.79
# normal rotation, probability, scaled offset, object coords, initial and per step noise
bl_info = {
"name": "MExtrude Plus1",
"author": "liero, Jimmy Hazevoet",
"version": (1, 3, 0),
"blender": (2, 77, 0),
"location": "View3D > Tool Shelf",
"description": "Repeat extrusions from faces to create organic shapes",
"warning": "",
"wiki_url": "",
"category": "Mesh"}
import bpy
import bmesh
import random
from bpy.types import Operator
from random import gauss
from math import radians
from mathutils import (
Euler, Vector,
)
from bpy.props import (
FloatProperty,
IntProperty,
BoolProperty,
)
def gloc(self, r):
return Vector((self.offx, self.offy, self.offz))
def vloc(self, r):
random.seed(self.ran + r)
return self.off * (1 + gauss(0, self.var1 / 3))
def nrot(self, n):
return Euler((radians(self.nrotx) * n[0],
radians(self.nroty) * n[1],
radians(self.nrotz) * n[2]), 'XYZ')
def vrot(self, r):
random.seed(self.ran + r)
return Euler((radians(self.rotx) + gauss(0, self.var2 / 3),
radians(self.roty) + gauss(0, self.var2 / 3),
radians(self.rotz) + gauss(0, self.var2 / 3)), 'XYZ')
def vsca(self, r):
random.seed(self.ran + r)
return self.sca * (1 + gauss(0, self.var3 / 3))
class MExtrude(Operator):
bl_idname = "object.mextrude"
bl_label = "Multi Extrude"
bl_description = ("Extrude selected Faces with Rotation,\n"
"Scaling, Variation, Randomization")
bl_options = {"REGISTER", "UNDO", "PRESET"}
off: FloatProperty(
name="Offset",
soft_min=0.001, soft_max=10,
min=-100, max=100,
default=1.0,
description="Translation"
)
offx: FloatProperty(
name="Loc X",
soft_min=-10.0, soft_max=10.0,
min=-100.0, max=100.0,
default=0.0,
description="Global Translation X"
)
offy: FloatProperty(
name="Loc Y",
soft_min=-10.0, soft_max=10.0,
min=-100.0, max=100.0,
default=0.0,
description="Global Translation Y"
)
offz: FloatProperty(
name="Loc Z",
soft_min=-10.0, soft_max=10.0,
min=-100.0, max=100.0,
default=0.0,
description="Global Translation Z"
)
rotx: FloatProperty(
name="Rot X",
min=-85, max=85,
soft_min=-30, soft_max=30,
default=0,
description="X Rotation"
)
roty: FloatProperty(
name="Rot Y",
min=-85, max=85,
soft_min=-30,
soft_max=30,
default=0,
description="Y Rotation"
)
rotz: FloatProperty(
name="Rot Z",
min=-85, max=85,
soft_min=-30, soft_max=30,
default=-0,
description="Z Rotation"
)
nrotx: FloatProperty(
name="N Rot X",
min=-85, max=85,
soft_min=-30, soft_max=30,
default=0,
description="Normal X Rotation"
)
nroty: FloatProperty(
name="N Rot Y",
min=-85, max=85,
soft_min=-30, soft_max=30,
default=0,
description="Normal Y Rotation"
)
nrotz: FloatProperty(
name="N Rot Z",
min=-85, max=85,
soft_min=-30, soft_max=30,
default=-0,
description="Normal Z Rotation"
)
sca: FloatProperty(
name="Scale",
min=0.01, max=10,
soft_min=0.5, soft_max=1.5,
default=1.0,
description="Scaling of the selected faces after extrusion"
)
var1: FloatProperty(
name="Offset Var", min=-10, max=10,
soft_min=-1, soft_max=1,
default=0,
description="Offset variation"
)
var2: FloatProperty(
name="Rotation Var",
min=-10, max=10,
soft_min=-1, soft_max=1,
default=0,
description="Rotation variation"
)
var3: FloatProperty(
name="Scale Noise",
min=-10, max=10,
soft_min=-1, soft_max=1,
default=0,
description="Scaling noise"
)
var4: IntProperty(
name="Probability",
min=0, max=100,
default=100,
description="Probability, chance of extruding a face"
)
num: IntProperty(
name="Repeat",
min=1, max=500,
soft_max=100,
default=5,
description="Repetitions"
)
ran: IntProperty(
name="Seed",
min=-9999, max=9999,
default=0,
description="Seed to feed random values"
)
opt1: BoolProperty(
name="Polygon coordinates",
default=True,
description="Polygon coordinates, Object coordinates"
)
opt2: BoolProperty(
name="Proportional offset",
default=False,
description="Scale * Offset"
)
opt3: BoolProperty(
name="Per step rotation noise",
default=False,
description="Per step rotation noise, Initial rotation noise"
)
opt4: BoolProperty(
name="Per step scale noise",
default=False,
description="Per step scale noise, Initial scale noise"
)
@classmethod
def poll(cls, context):
obj = context.object
return (obj and obj.type == 'MESH')
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
col.label(text="Transformations:")
col.prop(self, "off", slider=True)
col.prop(self, "offx", slider=True)
col.prop(self, "offy", slider=True)
col.prop(self, "offz", slider=True)
col = layout.column(align=True)
col.prop(self, "rotx", slider=True)
col.prop(self, "roty", slider=True)
col.prop(self, "rotz", slider=True)
col.prop(self, "nrotx", slider=True)
col.prop(self, "nroty", slider=True)
col.prop(self, "nrotz", slider=True)
col = layout.column(align=True)
col.prop(self, "sca", slider=True)
col = layout.column(align=True)
col.label(text="Variation settings:")
col.prop(self, "var1", slider=True)
col.prop(self, "var2", slider=True)
col.prop(self, "var3", slider=True)
col.prop(self, "var4", slider=True)
col.prop(self, "ran")
col = layout.column(align=False)
col.prop(self, 'num')
col = layout.column(align=True)
col.label(text="Options:")
col.prop(self, "opt1")
col.prop(self, "opt2")
col.prop(self, "opt3")
col.prop(self, "opt4")
def execute(self, context):
obj = bpy.context.object
om = obj.mode
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
origin = Vector([0.0, 0.0, 0.0])
# bmesh operations
bpy.ops.object.mode_set()
bm = bmesh.new()
bm.from_mesh(obj.data)
sel = [f for f in bm.faces if f.select]
after = []
# faces loop
for i, of in enumerate(sel):
nro = nrot(self, of.normal)
off = vloc(self, i)
loc = gloc(self, i)
of.normal_update()
# initial rotation noise
if self.opt3 is False:
rot = vrot(self, i)
# initial scale noise
if self.opt4 is False:
s = vsca(self, i)
# extrusion loop
for r in range(self.num):
# random probability % for extrusions
if self.var4 > int(random.random() * 100):
nf = of.copy()
nf.normal_update()
no = nf.normal.copy()
# face/obj coördinates
if self.opt1 is True:
ce = nf.calc_center_bounds()
else:
ce = origin
# per step rotation noise
if self.opt3 is True:
rot = vrot(self, i + r)
# per step scale noise
if self.opt4 is True:
s = vsca(self, i + r)
# proportional, scale * offset
if self.opt2 is True:
off = s * off
for v in nf.verts:
v.co -= ce
v.co.rotate(nro)
v.co.rotate(rot)
v.co += ce + loc + no * off
v.co = v.co.lerp(ce, 1 - s)
# extrude code from TrumanBlending
for a, b in zip(of.loops, nf.loops):
sf = bm.faces.new((a.vert, a.link_loop_next.vert,
b.link_loop_next.vert, b.vert))
sf.normal_update()
bm.faces.remove(of)
of = nf
after.append(of)
for v in bm.verts:
v.select = False
for e in bm.edges:
e.select = False
for f in after:
if f not in sel:
f.select = True
else:
f.select = False
bm.to_mesh(obj.data)
obj.data.update()
# restore user settings
bpy.ops.object.mode_set(mode=om)
if not len(sel):
self.report({"WARNING"},
"No suitable Face selection found. Operation cancelled")
return {'CANCELLED'}
return {'FINISHED'}
def register():
bpy.utils.register_module(__name__)
def unregister():
bpy.utils.unregister_module(__name__)
if __name__ == '__main__':
register()

View File

@ -1,823 +0,0 @@
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Offset Edges",
"author": "Hidesato Ikeya",
"version": (0, 2, 6),
"blender": (2, 70, 0),
"location": "VIEW3D > Edge menu(CTRL-E) > Offset Edges",
"description": "Offset Edges",
"warning": "",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
"Py/Scripts/Modeling/offset_edges",
"category": "Mesh"}
import bpy
import bmesh
from bpy.types import Operator
from math import sin, cos, pi, radians
from mathutils import Vector
from time import perf_counter
from bpy.props import (
BoolProperty,
FloatProperty,
EnumProperty,
)
# Globals
X_UP = Vector((1.0, .0, .0))
Y_UP = Vector((.0, 1.0, .0))
Z_UP = Vector((.0, .0, 1.0))
ZERO_VEC = Vector((.0, .0, .0))
ANGLE_90 = pi / 2
ANGLE_180 = pi
ANGLE_360 = 2 * pi
# switch performance logging
ENABLE_DEBUG = False
def calc_loop_normal(verts, fallback=Z_UP):
# Calculate normal from verts using Newell's method
normal = ZERO_VEC.copy()
if verts[0] is verts[-1]:
# Perfect loop
range_verts = range(1, len(verts))
else:
# Half loop
range_verts = range(0, len(verts))
for i in range_verts:
v1co, v2co = verts[i - 1].co, verts[i].co
normal.x += (v1co.y - v2co.y) * (v1co.z + v2co.z)
normal.y += (v1co.z - v2co.z) * (v1co.x + v2co.x)
normal.z += (v1co.x - v2co.x) * (v1co.y + v2co.y)
if normal != ZERO_VEC:
normal.normalize()
else:
normal = fallback
return normal
def collect_edges(bm):
set_edges_orig = set()
for e in bm.edges:
if e.select:
co_faces_selected = 0
for f in e.link_faces:
if f.select:
co_faces_selected += 1
if co_faces_selected == 2:
break
else:
set_edges_orig.add(e)
if not set_edges_orig:
return None
return set_edges_orig
def collect_loops(set_edges_orig):
set_edges_copy = set_edges_orig.copy()
loops = [] # [v, e, v, e, ... , e, v]
while set_edges_copy:
edge_start = set_edges_copy.pop()
v_left, v_right = edge_start.verts
lp = [v_left, edge_start, v_right]
reverse = False
while True:
edge = None
for e in v_right.link_edges:
if e in set_edges_copy:
if edge:
# Overlap detected.
return None
edge = e
set_edges_copy.remove(e)
if edge:
v_right = edge.other_vert(v_right)
lp.extend((edge, v_right))
continue
else:
if v_right is v_left:
# Real loop.
loops.append(lp)
break
elif reverse is False:
# Right side of half loop
# Reversing the loop to operate same procedure on the left side
lp.reverse()
v_right, v_left = v_left, v_right
reverse = True
continue
else:
# Half loop, completed
loops.append(lp)
break
return loops
def get_adj_ix(ix_start, vec_edges, half_loop):
# Get adjacent edge index, skipping zero length edges
len_edges = len(vec_edges)
if half_loop:
range_right = range(ix_start, len_edges)
range_left = range(ix_start - 1, -1, -1)
else:
range_right = range(ix_start, ix_start + len_edges)
range_left = range(ix_start - 1, ix_start - 1 - len_edges, -1)
ix_right = ix_left = None
for i in range_right:
# Right
i %= len_edges
if vec_edges[i] != ZERO_VEC:
ix_right = i
break
for i in range_left:
# Left
i %= len_edges
if vec_edges[i] != ZERO_VEC:
ix_left = i
break
if half_loop:
# If index of one side is None, assign another index
if ix_right is None:
ix_right = ix_left
if ix_left is None:
ix_left = ix_right
return ix_right, ix_left
def get_adj_faces(edges):
adj_faces = []
for e in edges:
adj_f = None
co_adj = 0
for f in e.link_faces:
# Search an adjacent face
# Selected face has precedence
if not f.hide and f.normal != ZERO_VEC:
adj_f = f
co_adj += 1
if f.select:
adj_faces.append(adj_f)
break
else:
if co_adj == 1:
adj_faces.append(adj_f)
else:
adj_faces.append(None)
return adj_faces
def get_edge_rail(vert, set_edges_orig):
co_edges = co_edges_selected = 0
vec_inner = None
for e in vert.link_edges:
if (e not in set_edges_orig and
(e.select or (co_edges_selected == 0 and not e.hide))):
v_other = e.other_vert(vert)
vec = v_other.co - vert.co
if vec != ZERO_VEC:
vec_inner = vec
if e.select:
co_edges_selected += 1
if co_edges_selected == 2:
return None
else:
co_edges += 1
if co_edges_selected == 1:
vec_inner.normalize()
return vec_inner
elif co_edges == 1:
# No selected edges, one unselected edge
vec_inner.normalize()
return vec_inner
else:
return None
def get_cross_rail(vec_tan, vec_edge_r, vec_edge_l, normal_r, normal_l):
# Cross rail is a cross vector between normal_r and normal_l
vec_cross = normal_r.cross(normal_l)
if vec_cross.dot(vec_tan) < .0:
vec_cross *= -1
cos_min = min(vec_tan.dot(vec_edge_r), vec_tan.dot(-vec_edge_l))
cos = vec_tan.dot(vec_cross)
if cos >= cos_min:
vec_cross.normalize()
return vec_cross
else:
return None
def move_verts(width, depth, verts, directions, geom_ex):
if geom_ex:
geom_s = geom_ex['side']
verts_ex = []
for v in verts:
for e in v.link_edges:
if e in geom_s:
verts_ex.append(e.other_vert(v))
break
verts = verts_ex
for v, (vec_width, vec_depth) in zip(verts, directions):
v.co += width * vec_width + depth * vec_depth
def extrude_edges(bm, edges_orig):
extruded = bmesh.ops.extrude_edge_only(bm, edges=edges_orig)['geom']
n_edges = n_faces = len(edges_orig)
n_verts = len(extruded) - n_edges - n_faces
geom = dict()
geom['verts'] = verts = set(extruded[:n_verts])
geom['edges'] = edges = set(extruded[n_verts:n_verts + n_edges])
geom['faces'] = set(extruded[n_verts + n_edges:])
geom['side'] = set(e for v in verts for e in v.link_edges if e not in edges)
return geom
def clean(bm, mode, edges_orig, geom_ex=None):
for f in bm.faces:
f.select = False
if geom_ex:
for e in geom_ex['edges']:
e.select = True
if mode == 'offset':
lis_geom = list(geom_ex['side']) + list(geom_ex['faces'])
bmesh.ops.delete(bm, geom=lis_geom, context=2)
else:
for e in edges_orig:
e.select = True
def collect_mirror_planes(edit_object):
mirror_planes = []
eob_mat_inv = edit_object.matrix_world.inverted()
for m in edit_object.modifiers:
if (m.type == 'MIRROR' and m.use_mirror_merge):
merge_limit = m.merge_threshold
if not m.mirror_object:
loc = ZERO_VEC
norm_x, norm_y, norm_z = X_UP, Y_UP, Z_UP
else:
mirror_mat_local = eob_mat_inv * m.mirror_object.matrix_world
loc = mirror_mat_local.to_translation()
norm_x, norm_y, norm_z, _ = mirror_mat_local.adjugated()
norm_x = norm_x.to_3d().normalized()
norm_y = norm_y.to_3d().normalized()
norm_z = norm_z.to_3d().normalized()
if m.use_x:
mirror_planes.append((loc, norm_x, merge_limit))
if m.use_y:
mirror_planes.append((loc, norm_y, merge_limit))
if m.use_z:
mirror_planes.append((loc, norm_z, merge_limit))
return mirror_planes
def get_vert_mirror_pairs(set_edges_orig, mirror_planes):
if mirror_planes:
set_edges_copy = set_edges_orig.copy()
vert_mirror_pairs = dict()
for e in set_edges_orig:
v1, v2 = e.verts
for mp in mirror_planes:
p_co, p_norm, mlimit = mp
v1_dist = abs(p_norm.dot(v1.co - p_co))
v2_dist = abs(p_norm.dot(v2.co - p_co))
if v1_dist <= mlimit:
# v1 is on a mirror plane
vert_mirror_pairs[v1] = mp
if v2_dist <= mlimit:
# v2 is on a mirror plane
vert_mirror_pairs[v2] = mp
if v1_dist <= mlimit and v2_dist <= mlimit:
# This edge is on a mirror_plane, so should not be offsetted
set_edges_copy.remove(e)
return vert_mirror_pairs, set_edges_copy
else:
return None, set_edges_orig
def get_mirror_rail(mirror_plane, vec_up):
p_norm = mirror_plane[1]
mirror_rail = vec_up.cross(p_norm)
if mirror_rail != ZERO_VEC:
mirror_rail.normalize()
# Project vec_up to mirror_plane
vec_up = vec_up - vec_up.project(p_norm)
vec_up.normalize()
return mirror_rail, vec_up
else:
return None, vec_up
def reorder_loop(verts, edges, lp_normal, adj_faces):
for i, adj_f in enumerate(adj_faces):
if adj_f is None:
continue
v1, v2 = verts[i], verts[i + 1]
fv = tuple(adj_f.verts)
if fv[fv.index(v1) - 1] is v2:
# Align loop direction
verts.reverse()
edges.reverse()
adj_faces.reverse()
if lp_normal.dot(adj_f.normal) < .0:
lp_normal *= -1
break
else:
# All elements in adj_faces are None
for v in verts:
if v.normal != ZERO_VEC:
if lp_normal.dot(v.normal) < .0:
verts.reverse()
edges.reverse()
lp_normal *= -1
break
return verts, edges, lp_normal, adj_faces
def get_directions(lp, vec_upward, normal_fallback, vert_mirror_pairs, **options):
opt_follow_face = options['follow_face']
opt_edge_rail = options['edge_rail']
opt_er_only_end = options['edge_rail_only_end']
opt_threshold = options['threshold']
verts, edges = lp[::2], lp[1::2]
set_edges = set(edges)
lp_normal = calc_loop_normal(verts, fallback=normal_fallback)
# Loop order might be changed below
if lp_normal.dot(vec_upward) < .0:
# Make this loop's normal towards vec_upward
verts.reverse()
edges.reverse()
lp_normal *= -1
if opt_follow_face:
adj_faces = get_adj_faces(edges)
verts, edges, lp_normal, adj_faces = \
reorder_loop(verts, edges, lp_normal, adj_faces)
else:
adj_faces = (None, ) * len(edges)
# Loop order might be changed above
vec_edges = tuple((e.other_vert(v).co - v.co).normalized()
for v, e in zip(verts, edges))
if verts[0] is verts[-1]:
# Real loop. Popping last vertex
verts.pop()
HALF_LOOP = False
else:
# Half loop
HALF_LOOP = True
len_verts = len(verts)
directions = []
for i in range(len_verts):
vert = verts[i]
ix_right, ix_left = i, i - 1
VERT_END = False
if HALF_LOOP:
if i == 0:
# First vert
ix_left = ix_right
VERT_END = True
elif i == len_verts - 1:
# Last vert
ix_right = ix_left
VERT_END = True
edge_right, edge_left = vec_edges[ix_right], vec_edges[ix_left]
face_right, face_left = adj_faces[ix_right], adj_faces[ix_left]
norm_right = face_right.normal if face_right else lp_normal
norm_left = face_left.normal if face_left else lp_normal
if norm_right.angle(norm_left) > opt_threshold:
# Two faces are not flat
two_normals = True
else:
two_normals = False
tan_right = edge_right.cross(norm_right).normalized()
tan_left = edge_left.cross(norm_left).normalized()
tan_avr = (tan_right + tan_left).normalized()
norm_avr = (norm_right + norm_left).normalized()
rail = None
if two_normals or opt_edge_rail:
# Get edge rail
# edge rail is a vector of an inner edge
if two_normals or (not opt_er_only_end) or VERT_END:
rail = get_edge_rail(vert, set_edges)
if vert_mirror_pairs and VERT_END:
if vert in vert_mirror_pairs:
rail, norm_avr = get_mirror_rail(vert_mirror_pairs[vert], norm_avr)
if (not rail) and two_normals:
# Get cross rail
# Cross rail is a cross vector between norm_right and norm_left
rail = get_cross_rail(
tan_avr, edge_right, edge_left, norm_right, norm_left)
if rail:
dot = tan_avr.dot(rail)
if dot > .0:
tan_avr = rail
elif dot < .0:
tan_avr = -rail
vec_plane = norm_avr.cross(tan_avr)
e_dot_p_r = edge_right.dot(vec_plane)
e_dot_p_l = edge_left.dot(vec_plane)
if e_dot_p_r or e_dot_p_l:
if e_dot_p_r > e_dot_p_l:
vec_edge, e_dot_p = edge_right, e_dot_p_r
else:
vec_edge, e_dot_p = edge_left, e_dot_p_l
vec_tan = (tan_avr - tan_avr.project(vec_edge)).normalized()
# Make vec_tan perpendicular to vec_edge
vec_up = vec_tan.cross(vec_edge)
vec_width = vec_tan - (vec_tan.dot(vec_plane) / e_dot_p) * vec_edge
vec_depth = vec_up - (vec_up.dot(vec_plane) / e_dot_p) * vec_edge
else:
vec_width = tan_avr
vec_depth = norm_avr
directions.append((vec_width, vec_depth))
return verts, directions
angle_presets = {'': 0,
'15°': radians(15),
'30°': radians(30),
'45°': radians(45),
'60°': radians(60),
'75°': radians(75),
'90°': radians(90),
}
def use_cashes(self, context):
self.caches_valid = True
def assign_angle_presets(self, context):
use_cashes(self, context)
self.angle = angle_presets[self.angle_presets]
class OffsetEdges(Operator):
bl_idname = "mesh.offset_edges"
bl_label = "Offset Edges"
bl_description = ("Extrude, Move or Offset the selected Edges\n"
"Operates only on separate Edge loops selections")
bl_options = {'REGISTER', 'UNDO'}
geometry_mode: EnumProperty(
items=[('offset', "Offset", "Offset edges"),
('extrude', "Extrude", "Extrude edges"),
('move', "Move", "Move selected edges")],
name="Geometry mode",
default='offset',
update=use_cashes
)
width: FloatProperty(
name="Width",
default=.2,
precision=4, step=1,
update=use_cashes
)
flip_width: BoolProperty(
name="Flip Width",
default=False,
description="Flip width direction",
update=use_cashes
)
depth: FloatProperty(
name="Depth",
default=.0,
precision=4, step=1,
update=use_cashes
)
flip_depth: BoolProperty(
name="Flip Depth",
default=False,
description="Flip depth direction",
update=use_cashes
)
depth_mode: EnumProperty(
items=[('angle', "Angle", "Angle"),
('depth', "Depth", "Depth")],
name="Depth mode",
default='angle',
update=use_cashes
)
angle: FloatProperty(
name="Angle", default=0,
precision=3, step=.1,
min=-2 * pi, max=2 * pi,
subtype='ANGLE',
description="Angle",
update=use_cashes
)
flip_angle: BoolProperty(
name="Flip Angle",
default=False,
description="Flip Angle",
update=use_cashes
)
follow_face: BoolProperty(
name="Follow Face",
default=False,
description="Offset along faces around"
)
mirror_modifier: BoolProperty(
name="Mirror Modifier",
default=False,
description="Take into account of Mirror modifier"
)
edge_rail: BoolProperty(
name="Edge Rail",
default=False,
description="Align vertices along inner edges"
)
edge_rail_only_end: BoolProperty(
name="Edge Rail Only End",
default=False,
description="Apply edge rail to end verts only"
)
threshold: FloatProperty(
name="Flat Face Threshold",
default=radians(0.05), precision=5,
step=1.0e-4, subtype='ANGLE',
description="If difference of angle between two adjacent faces is "
"below this value, those faces are regarded as flat",
options={'HIDDEN'}
)
caches_valid: BoolProperty(
name="Caches Valid",
default=False,
options={'HIDDEN'}
)
angle_presets: EnumProperty(
items=[('', "", ""),
('15°', "15°", "15°"),
('30°', "30°", "30°"),
('45°', "45°", "45°"),
('60°', "60°", "60°"),
('75°', "75°", "75°"),
('90°', "90°", "90°"), ],
name="Angle Presets",
default='',
update=assign_angle_presets
)
_cache_offset_infos = None
_cache_edges_orig_ixs = None
@classmethod
def poll(self, context):
return context.mode == 'EDIT_MESH'
def draw(self, context):
layout = self.layout
layout.prop(self, 'geometry_mode', text="")
row = layout.row(align=True)
row.prop(self, 'width')
row.prop(self, 'flip_width', icon='ARROW_LEFTRIGHT', icon_only=True)
layout.prop(self, 'depth_mode', expand=True)
if self.depth_mode == 'angle':
d_mode = 'angle'
flip = 'flip_angle'
else:
d_mode = 'depth'
flip = 'flip_depth'
row = layout.row(align=True)
row.prop(self, d_mode)
row.prop(self, flip, icon='ARROW_LEFTRIGHT', icon_only=True)
if self.depth_mode == 'angle':
layout.prop(self, 'angle_presets', text="Presets", expand=True)
layout.separator()
layout.prop(self, 'follow_face')
row = layout.row()
row.prop(self, 'edge_rail')
if self.edge_rail:
row.prop(self, 'edge_rail_only_end', text="OnlyEnd", toggle=True)
layout.prop(self, 'mirror_modifier')
layout.operator('mesh.offset_edges', text="Repeat")
if self.follow_face:
layout.separator()
layout.prop(self, 'threshold', text="Threshold")
def get_offset_infos(self, bm, edit_object):
if self.caches_valid and self._cache_offset_infos is not None:
# Return None, indicating to use cache
return None, None
if ENABLE_DEBUG:
time = perf_counter()
set_edges_orig = collect_edges(bm)
if set_edges_orig is None:
self.report({'WARNING'},
"No edges selected or edge loops could not be determined")
return False, False
if self.mirror_modifier:
mirror_planes = collect_mirror_planes(edit_object)
vert_mirror_pairs, set_edges = \
get_vert_mirror_pairs(set_edges_orig, mirror_planes)
if set_edges:
set_edges_orig = set_edges
else:
vert_mirror_pairs = None
else:
vert_mirror_pairs = None
loops = collect_loops(set_edges_orig)
if loops is None:
self.report({'WARNING'},
"Overlap detected. Select non-overlapping edge loops")
return False, False
vec_upward = (X_UP + Y_UP + Z_UP).normalized()
# vec_upward is used to unify loop normals when follow_face is off
normal_fallback = Z_UP
# normal_fallback = Vector(context.region_data.view_matrix[2][:3])
# normal_fallback is used when loop normal cannot be calculated
follow_face = self.follow_face
edge_rail = self.edge_rail
er_only_end = self.edge_rail_only_end
threshold = self.threshold
offset_infos = []
for lp in loops:
verts, directions = get_directions(
lp, vec_upward, normal_fallback, vert_mirror_pairs,
follow_face=follow_face, edge_rail=edge_rail,
edge_rail_only_end=er_only_end,
threshold=threshold)
if verts:
offset_infos.append((verts, directions))
# Saving caches
self._cache_offset_infos = _cache_offset_infos = []
for verts, directions in offset_infos:
v_ixs = tuple(v.index for v in verts)
_cache_offset_infos.append((v_ixs, directions))
self._cache_edges_orig_ixs = tuple(e.index for e in set_edges_orig)
if ENABLE_DEBUG:
print("Preparing OffsetEdges: ", perf_counter() - time)
return offset_infos, set_edges_orig
def do_offset_and_free(self, bm, me, offset_infos=None, set_edges_orig=None):
# If offset_infos is None, use caches
# Makes caches invalid after offset
if ENABLE_DEBUG:
time = perf_counter()
if offset_infos is None:
# using cache
bmverts = tuple(bm.verts)
bmedges = tuple(bm.edges)
edges_orig = [bmedges[ix] for ix in self._cache_edges_orig_ixs]
verts_directions = []
for ix_vs, directions in self._cache_offset_infos:
verts = tuple(bmverts[ix] for ix in ix_vs)
verts_directions.append((verts, directions))
else:
verts_directions = offset_infos
edges_orig = list(set_edges_orig)
if self.depth_mode == 'angle':
w = self.width if not self.flip_width else -self.width
angle = self.angle if not self.flip_angle else -self.angle
width = w * cos(angle)
depth = w * sin(angle)
else:
width = self.width if not self.flip_width else -self.width
depth = self.depth if not self.flip_depth else -self.depth
# Extrude
if self.geometry_mode == 'move':
geom_ex = None
else:
geom_ex = extrude_edges(bm, edges_orig)
for verts, directions in verts_directions:
move_verts(width, depth, verts, directions, geom_ex)
clean(bm, self.geometry_mode, edges_orig, geom_ex)
bpy.ops.object.mode_set(mode="OBJECT")
bm.to_mesh(me)
bpy.ops.object.mode_set(mode="EDIT")
bm.free()
self.caches_valid = False # Make caches invalid
if ENABLE_DEBUG:
print("OffsetEdges offset: ", perf_counter() - time)
def execute(self, context):
# In edit mode
edit_object = context.edit_object
bpy.ops.object.mode_set(mode="OBJECT")
me = edit_object.data
bm = bmesh.new()
bm.from_mesh(me)
offset_infos, edges_orig = self.get_offset_infos(bm, edit_object)
if offset_infos is False:
bpy.ops.object.mode_set(mode="EDIT")
return {'CANCELLED'}
self.do_offset_and_free(bm, me, offset_infos, edges_orig)
return {'FINISHED'}
def restore_original_and_free(self, context):
self.caches_valid = False # Make caches invalid
context.area.header_text_set(None)
me = context.edit_object.data
bpy.ops.object.mode_set(mode="OBJECT")
self._bm_orig.to_mesh(me)
bpy.ops.object.mode_set(mode="EDIT")
self._bm_orig.free()
context.area.header_text_set(None)
def invoke(self, context, event):
# In edit mode
edit_object = context.edit_object
me = edit_object.data
bpy.ops.object.mode_set(mode="OBJECT")
for p in me.polygons:
if p.select:
self.follow_face = True
break
self.caches_valid = False
bpy.ops.object.mode_set(mode="EDIT")
return self.execute(context)
def register():
bpy.utils.register_module(__name__)
def unregister():
bpy.utils.unregister_module(__name__)
if __name__ == '__main__':
register()

View File

@ -1,568 +0,0 @@
# -*- coding: utf-8 -*-
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Pen Tool",
"author": "zmj100",
"version": (0, 3, 1),
"blender": (2, 78, 0),
"location": "View3D > Tool Shelf",
"description": "",
"warning": "",
"wiki_url": "",
"category": "Mesh",
}
import bpy
import bpy_extras
import blf
import bgl
import bmesh
from bpy.types import (
Operator,
PropertyGroup,
Panel
)
from bpy.props import (
FloatProperty,
IntProperty,
PointerProperty,
BoolProperty
)
from bpy_extras.view3d_utils import (
region_2d_to_location_3d,
location_3d_to_region_2d,
)
from mathutils import (
Vector,
Matrix,
)
from math import degrees
def edit_mode_out():
bpy.ops.object.mode_set(mode='OBJECT')
def edit_mode_in():
bpy.ops.object.mode_set(mode='EDIT')
def get_direction_(bme, list_, ob_act):
n = len(list_)
for i in range(n):
p = ob_act.matrix_world * (bme.verts[list_[i]].co).copy()
p1 = ob_act.matrix_world * (bme.verts[list_[(i - 1) % n]].co).copy()
p2 = ob_act.matrix_world * (bme.verts[list_[(i + 1) % n]].co).copy()
if p == p1 or p == p2:
continue
ang = round(degrees((p - p1).angle((p - p2), any)))
if ang == 0 or ang == 180:
continue
elif ang != 0 or ang != 180:
return(((p - p1).cross((p - p2))).normalized())
break
def store_restore_view(context, store=True):
if not context.scene.pen_tool_props.restore_view:
return
if store is True:
# copy the original view_matrix and rotation for restoring
pt_buf.store_view_matrix = context.space_data.region_3d.view_matrix.copy()
pt_buf.view_location = context.space_data.region_3d.view_location.copy()
else:
context.space_data.region_3d.view_matrix = pt_buf.store_view_matrix
context.space_data.region_3d.view_location = pt_buf.view_location
def align_view_to_face_(context, bme, f):
store_restore_view(context, True)
ob_act = context.active_object
list_e = [[v.index for v in e.verts] for e in f.edges][0]
vec0 = -get_direction_(bme, [v.index for v in f.verts], ob_act)
vec1 = ((ob_act.matrix_world * bme.verts[list_e[0]].co.copy()) -
(ob_act.matrix_world * bme.verts[list_e[1]].co.copy())).normalized()
vec2 = (vec0.cross(vec1)).normalized()
context.space_data.region_3d.view_matrix = ((Matrix((vec1, vec2, vec0))).to_4x4()).inverted()
context.space_data.region_3d.view_location = f.calc_center_median()
def draw_callback_px(self, context):
font_id = 0
alpha = context.scene.pen_tool_props.a
font_size = context.scene.pen_tool_props.fs
bgl.glColor4f(0.0, 0.6, 1.0, alpha)
bgl.glPointSize(4.0)
bgl.glBegin(bgl.GL_POINTS)
bgl.glVertex2f(pt_buf.x, pt_buf.y)
bgl.glEnd()
bgl.glDisable(bgl.GL_BLEND)
# location 3d
if context.scene.pen_tool_props.b2 is True:
mloc3d = region_2d_to_location_3d(
context.region,
context.space_data.region_3d, Vector((pt_buf.x, pt_buf.y)),
pt_buf.depth_location
)
blf.position(font_id, pt_buf.x + 15, pt_buf.y - 15, 0)
blf.size(font_id, font_size, context.preferences.system.dpi)
blf.draw(font_id,
'(' + str(round(mloc3d[0], 4)) + ', ' + str(round(mloc3d[1], 4)) +
', ' + str(round(mloc3d[2], 4)) + ')')
n = len(pt_buf.list_m_loc_3d)
if n != 0:
# add points
bgl.glEnable(bgl.GL_BLEND)
bgl.glPointSize(4.0)
bgl.glBegin(bgl.GL_POINTS)
for i in pt_buf.list_m_loc_3d:
loc_0 = location_3d_to_region_2d(
context.region, context.space_data.region_3d, i
)
bgl.glVertex2f(loc_0[0], loc_0[1])
bgl.glEnd()
bgl.glDisable(bgl.GL_BLEND)
# text next to the mouse
m_loc_3d = region_2d_to_location_3d(
context.region,
context.space_data.region_3d, Vector((pt_buf.x, pt_buf.y)),
pt_buf.depth_location
)
vec0 = pt_buf.list_m_loc_3d[-1] - m_loc_3d
blf.position(font_id, pt_buf.x + 15, pt_buf.y + 15, 0)
blf.size(font_id, font_size, context.preferences.system.dpi)
blf.draw(font_id, str(round(vec0.length, 4)))
# angle first after mouse
if n >= 2:
vec1 = pt_buf.list_m_loc_3d[-2] - pt_buf.list_m_loc_3d[-1]
if vec0.length == 0.0 or vec1.length == 0.0:
pass
else:
ang = vec0.angle(vec1)
if round(degrees(ang), 2) == 180.0:
text_0 = '0.0'
elif round(degrees(ang), 2) == 0.0:
text_0 = '180.0'
else:
text_0 = str(round(degrees(ang), 2))
loc_4 = location_3d_to_region_2d(
context.region,
context.space_data.region_3d,
pt_buf.list_m_loc_3d[-1]
)
bgl.glColor4f(0.0, 1.0, 0.525, alpha)
blf.position(font_id, loc_4[0] + 10, loc_4[1] + 10, 0)
blf.size(font_id, font_size, context.preferences.system.dpi)
blf.draw(font_id, text_0 + '')
bgl.glLineStipple(4, 0x5555)
bgl.glEnable(bgl.GL_LINE_STIPPLE) # enable line stipple
bgl.glColor4f(0.0, 0.6, 1.0, alpha)
# draw line between last point and mouse
bgl.glEnable(bgl.GL_BLEND)
bgl.glBegin(bgl.GL_LINES)
loc_1 = location_3d_to_region_2d(
context.region,
context.space_data.region_3d,
pt_buf.list_m_loc_3d[-1]
)
bgl.glVertex2f(loc_1[0], loc_1[1])
bgl.glVertex2f(pt_buf.x, pt_buf.y)
bgl.glEnd()
bgl.glDisable(bgl.GL_BLEND)
# draw lines between points
bgl.glEnable(bgl.GL_BLEND)
bgl.glBegin(bgl.GL_LINE_STRIP)
for j in pt_buf.list_m_loc_3d:
loc_2 = location_3d_to_region_2d(context.region, context.space_data.region_3d, j)
bgl.glVertex2f(loc_2[0], loc_2[1])
bgl.glEnd()
bgl.glDisable(bgl.GL_BLEND)
bgl.glDisable(bgl.GL_LINE_STIPPLE) # disable line stipple
# draw line length between points
if context.scene.pen_tool_props.b1 is True:
for k in range(n - 1):
loc_3 = location_3d_to_region_2d(
context.region, context.space_data.region_3d,
(pt_buf.list_m_loc_3d[k] + pt_buf.list_m_loc_3d[(k + 1) % n]) * 0.5
)
blf.position(font_id, loc_3[0] + 10, loc_3[1] + 10, 0)
blf.size(font_id, font_size, context.preferences.system.dpi)
blf.draw(font_id,
str(round((pt_buf.list_m_loc_3d[k] - pt_buf.list_m_loc_3d[(k + 1) % n]).length, 4)))
# draw all angles
if context.scene.pen_tool_props.b0 is True:
for h in range(n - 1):
if n >= 2:
if h == 0:
pass
else:
vec_ = pt_buf.list_m_loc_3d[h] - pt_buf.list_m_loc_3d[(h - 1) % n]
vec_1_ = pt_buf.list_m_loc_3d[h]
vec_2_ = pt_buf.list_m_loc_3d[(h - 1) % n]
if vec_.length == 0.0 or vec_1_.length == 0.0 or vec_2_.length == 0.0:
pass
else:
ang = vec_.angle(vec_1_ - vec_2_)
if round(degrees(ang)) == 0.0:
pass
else:
loc_4 = location_3d_to_region_2d(
context.region, context.space_data.region_3d,
pt_buf.list_m_loc_3d[h]
)
bgl.glColor4f(0.0, 1.0, 0.525, alpha)
blf.position(font_id, loc_4[0] + 10, loc_4[1] + 10, 0)
blf.size(font_id, font_size, context.preferences.system.dpi)
blf.draw(font_id, str(round(degrees(ang), 2)) + '')
# tools on / off
bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
blf.position(font_id, self.text_location, 20, 0)
blf.size(font_id, 15, context.preferences.system.dpi)
blf.draw(font_id, "Draw On")
blf.position(font_id, self.text_location, 40, 0)
blf.draw(font_id, "Extrude On" if pt_buf.ctrl else "Extrude Off")
class pen_tool_properties(PropertyGroup):
a: FloatProperty(
name="Alpha",
description="Set Font Alpha",
default=1.0,
min=0.1, max=1.0,
step=10,
precision=1
)
fs: IntProperty(
name="Size",
description="Set Font Size",
default=14,
min=12, max=40,
step=1
)
b0: BoolProperty(
name="Angles",
description="Display All Angles on Drawn Edges",
default=False
)
b1: BoolProperty(
name="Edge Length",
description="Display All Lengths of Drawn Edges",
default=False
)
b2: BoolProperty(
name="Mouse Location 3D",
description="Display the location coordinates of the mouse cursor",
default=False
)
restore_view: BoolProperty(
name="Restore View",
description="After the tool has finished, is the Viewport restored\n"
"to it's previous state",
default=True
)
class pt_buf():
list_m_loc_2d = []
list_m_loc_3d = []
x = 0
y = 0
sws = 'off'
depth_location = Vector((0.0, 0.0, 0.0))
alt = False
shift = False
ctrl = False
store_view_matrix = Matrix()
view_location = (0.0, 0.0, 0.0)
# ------ Panel ------
class pen_tool_panel(Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
bl_category = "Tools"
bl_label = "Pen Tool"
bl_context = "mesh_edit"
bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
layout = self.layout
pen_tool_props = context.scene.pen_tool_props
if pt_buf.sws == "on":
layout.active = False
layout.label(text="Pen Tool Active", icon="INFO")
else:
col = layout.column(align=True)
col.label(text="Font:")
col.prop(pen_tool_props, "fs", text="Size", slider=True)
col.prop(pen_tool_props, "a", text="Alpha", slider=True)
col = layout.column(align=True)
col.label(text="Settings:")
col.prop(pen_tool_props, "b0", text="Angles", toggle=True)
col.prop(pen_tool_props, "b1", text="Edge Length", toggle=True)
col.prop(pen_tool_props, "b2", text="Mouse Location 3D", toggle=True)
col.prop(pen_tool_props, "restore_view", text="Restore View", toggle=True)
split = layout.split(0.80, align=True)
split.operator("pen_tool.operator", text="Draw")
split.operator("mesh.extra_tools_help",
icon="LAYER_USED").help_ids = "mesh_pen_tool"
# Operator
class pen_tool_operator(Operator):
bl_idname = "pen_tool.operator"
bl_label = "Pen Tool"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
text_location: IntProperty(
name="",
default=0,
options={'HIDDEN'}
)
@classmethod
def poll(cls, context):
# do not run in object mode
return (context.active_object and context.active_object.type == 'MESH' and
context.mode == 'EDIT_MESH')
def execute(self, context):
edit_mode_out()
ob_act = context.active_object
bme = bmesh.new()
bme.from_mesh(ob_act.data)
mtrx = ob_act.matrix_world.inverted() # ob_act matrix world inverted
# add vertices
list_ = []
for i in pt_buf.list_m_loc_3d:
bme.verts.new(mtrx * i)
bme.verts.index_update()
bme.verts.ensure_lookup_table()
list_.append(bme.verts[-1])
# add edges
n = len(list_)
for j in range(n - 1):
bme.edges.new((list_[j], list_[(j + 1) % n]))
bme.edges.index_update()
bme.to_mesh(ob_act.data)
store_restore_view(context, False)
edit_mode_in()
pt_buf.list_m_loc_2d[:] = []
pt_buf.list_m_loc_3d[:] = []
pt_buf.depth_location = Vector((0.0, 0.0, 0.0))
pt_buf.store_view_matrix = Matrix()
pt_buf.view_location = (0.0, 0.0, 0.0)
pt_buf.ctrl = False
context.area.tag_redraw()
return {'FINISHED'}
def modal(self, context, event):
context.area.tag_redraw()
# allow moving in the 3D View
if event.type in {
'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE',
'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4', 'NUMPAD_6',
'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9', 'NUMPAD_5'}:
return {'PASS_THROUGH'}
if event.type in {'LEFT_ALT', 'RIGHT_ALT'}:
if event.value == 'PRESS':
pt_buf.alt = True
if event.value == 'RELEASE':
pt_buf.alt = False
return {'RUNNING_MODAL'}
elif event.type in {'LEFT_CTRL', 'RIGHT_CTRL'}:
if event.value == 'PRESS':
pt_buf.ctrl = not pt_buf.ctrl
return {'RUNNING_MODAL'}
elif event.type in {'LEFT_SHIFT', 'RIGHT_SHIFT'}:
if event.value == 'PRESS':
pt_buf.shift = True
if event.value == 'RELEASE':
pt_buf.shift = False
return {'RUNNING_MODAL'}
elif event.type == 'MOUSEMOVE':
if pt_buf.list_m_loc_2d != []:
pt_buf_list_m_loc_3d_last_2d = location_3d_to_region_2d(
context.region,
context.space_data.region_3d,
pt_buf.list_m_loc_3d[-1]
)
if pt_buf.alt is True:
pt_buf.x = pt_buf_list_m_loc_3d_last_2d[0]
pt_buf.y = event.mouse_region_y
elif pt_buf.shift is True:
pt_buf.x = event.mouse_region_x
pt_buf.y = pt_buf_list_m_loc_3d_last_2d[1]
else:
pt_buf.x = event.mouse_region_x
pt_buf.y = event.mouse_region_y
else:
pt_buf.x = event.mouse_region_x
pt_buf.y = event.mouse_region_y
elif event.type == 'LEFTMOUSE':
if event.value == 'PRESS':
mouse_loc_2d = Vector((pt_buf.x, pt_buf.y))
pt_buf.list_m_loc_2d.append(mouse_loc_2d)
mouse_loc_3d = region_2d_to_location_3d(
context.region, context.space_data.region_3d,
mouse_loc_2d, pt_buf.depth_location
)
pt_buf.list_m_loc_3d.append(mouse_loc_3d)
pt_buf.depth_location = pt_buf.list_m_loc_3d[-1] # <-- depth location
# run Extrude at cursor
if pt_buf.ctrl:
try:
bpy.ops.mesh.dupli_extrude_cursor('INVOKE_DEFAULT', rotate_source=False)
except:
pass
elif event.value == 'RELEASE':
pass
elif event.type == 'RIGHTMOUSE':
context.space_data.draw_handler_remove(self._handle_px, 'WINDOW')
self.execute(context)
pt_buf.sws = 'off'
return {'FINISHED'}
elif event.type == 'ESC':
context.space_data.draw_handler_remove(self._handle_px, 'WINDOW')
store_restore_view(context, False)
pt_buf.list_m_loc_2d[:] = []
pt_buf.list_m_loc_3d[:] = []
pt_buf.depth_location = Vector((0.0, 0.0, 0.0))
pt_buf.sws = 'off'
pt_buf.store_view_matrix = Matrix()
pt_buf.view_location = (0.0, 0.0, 0.0)
pt_buf.ctrl = False
return {'CANCELLED'}
# Return has to be modal or the tool can crash
# It's better to define PASS_THROUGH as the exception and not the default
return {'RUNNING_MODAL'}
def invoke(self, context, event):
bme = bmesh.from_edit_mesh(context.active_object.data)
list_f = [f for f in bme.faces if f.select]
if len(list_f) != 0:
f = list_f[0]
pt_buf.depth_location = f.calc_center_median()
align_view_to_face_(context, bme, f)
if context.area.type == 'VIEW_3D':
# pre-compute the text location (thanks to the Carver add-on)
self.text_location = 100
overlap = context.preferences.system.use_region_overlap
for region in context.area.regions:
if region.type == "WINDOW":
self.text_location = region.width - 100
if overlap:
for region in context.area.regions:
# The Properties Region on the right is of UI type
if region.type == "UI":
self.text_location = self.text_location - region.width
if pt_buf.sws == 'on':
return {'RUNNING_MODAL'}
elif pt_buf.sws != 'on':
context.window_manager.modal_handler_add(self)
self._handle_px = context.space_data.draw_handler_add(
draw_callback_px,
(self, context),
'WINDOW', 'POST_PIXEL'
)
pt_buf.sws = 'on'
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "Pen Tool: Operation Cancelled. View3D not found")
return {'CANCELLED'}
class_list = (
pen_tool_panel,
pen_tool_operator,
pen_tool_properties
)
KEYMAPS = (
# First, keymap identifiers (last bool is True for modal km).
(("3D View", "VIEW_3D", "WINDOW", False), (
# Then a tuple of keymap items, defined by a dict of kwargs
# for the km new func, and a tuple of tuples (name, val)
# for ops properties, if needing non-default values.
({"idname": pen_tool_operator.bl_idname, "type": 'D', "value": 'PRESS', "ctrl": True},
()),
)),
)
def register():
for c in class_list:
bpy.utils.register_class(c)
bpy.types.Scene.pen_tool_props = PointerProperty(type=pen_tool_properties)
bpy_extras.keyconfig_utils.addon_keymap_register(bpy.context.window_manager, KEYMAPS)
def unregister():
bpy_extras.keyconfig_utils.addon_keymap_unregister(bpy.context.window_manager, KEYMAPS)
del bpy.types.Scene.pen_tool_props
for c in class_list:
bpy.utils.unregister_class(c)
if __name__ == "__main__":
register()

View File

@ -1,70 +0,0 @@
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
# ##### END GPL LICENSE BLOCK #####
# menu & updates by meta-androcto #
# contributed to by :
# Macouno, dustractor, liero, lijenstina, #
# CoDEmanX, Dolf Veenvliet, meta-androcto #
bl_info = {
"name": "Select Tools",
"author": "Multiple Authors",
"version": (0, 3, 1),
"blender": (2, 64, 0),
"location": "Editmode Select Menu/Toolshelf Tools Tab",
"description": "Adds More vert/face/edge select modes.",
"warning": "",
"wiki_url": "",
"category": "Mesh"
}
if "bpy" in locals():
import importlib
importlib.reload(mesh_select_by_direction)
importlib.reload(mesh_select_by_edge_length)
importlib.reload(mesh_select_by_pi)
importlib.reload(mesh_select_by_type)
importlib.reload(mesh_select_connected_faces)
importlib.reload(mesh_index_select)
importlib.reload(mesh_selection_topokit)
importlib.reload(mesh_info_select)
else:
from . import mesh_select_by_direction
from . import mesh_select_by_edge_length
from . import mesh_select_by_pi
from . import mesh_select_by_type
from . import mesh_select_connected_faces
from . import mesh_index_select
from . import mesh_selection_topokit
from . import mesh_info_select
import bpy
# Register
def register():
bpy.utils.register_module(__name__)
def unregister():
bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
register()

View File

@ -1,168 +0,0 @@
# gpl author: liero
bl_info = {
"name": "Select by index",
"author": "liero",
"version": (0, 2),
"blender": (2, 55, 0),
"location": "View3D > Tool Shelf",
"description": "Select mesh data by index / area / length / cursor",
"category": "Mesh",
}
import bpy
from bpy.types import Operator
from bpy.props import (
BoolProperty,
FloatProperty,
EnumProperty,
)
class SelVertEdgeFace(Operator):
bl_idname = "mesh.select_vert_edge_face_index"
bl_label = "Select mesh index"
bl_description = "Select Vertices, Edges, Faces by their indices"
bl_options = {"REGISTER", "UNDO"}
select_type: EnumProperty(
items=[
('VERT', "Vertices", "Select Vertices by index"),
('EDGE', "Edges", "Select Edges by index"),
('FACE', "Faces", "Select Faces by index"),
],
name="Selection Mode",
description="",
default='VERT',
)
indice: FloatProperty(
name="Selected",
default=0,
min=0, max=100,
description="Percentage of selection",
precision=2,
subtype="PERCENTAGE"
)
delta: BoolProperty(
name="Use Parameter",
default=False,
description="Select by Index / Parameter"
)
flip: BoolProperty(
name="Reverse Order",
default=False,
description="Reverse selecting order"
)
start_new: BoolProperty(
name="Fresh Start",
default=False,
description="Start from no previous selection\n"
"If unchecked the previous selection is kept"
)
delta_text = {'VERT': "Use Cursor",
'EDGE': "Use Edges' Length",
'FACE': "Use Faces' Area"}
@classmethod
def poll(cls, context):
return (context.object is not None and context.object.type == 'MESH')
def draw(self, context):
layout = self.layout
layout.label(text="Selection Type:")
layout.prop(self, "select_type", text="")
layout.separator()
layout.label(text="Selected:")
layout.prop(self, "indice", text="", slider=True)
d_text = self.delta_text[self.select_type]
layout.prop(self, "delta", text=d_text)
layout.prop(self, "flip")
layout.prop(self, "start_new")
def execute(self, context):
obj = bpy.context.object
if self.start_new:
bpy.ops.mesh.select_all(action='DESELECT')
# Selection mode - Vertex, Edge, Face
if self.select_type == 'VERT':
bpy.context.tool_settings.mesh_select_mode = [True, False, False]
ver = obj.data.vertices
loc = context.scene.cursor.location
sel = []
for v in ver:
d = v.co - loc
sel.append((d.length, v.index))
sel.sort(reverse=self.flip)
bpy.ops.object.mode_set()
valor = round(len(sel) / 100 * self.indice)
if self.delta:
for i in range(len(sel[:valor])):
ver[sel[i][1]].select = True
else:
for i in range(len(sel[:valor])):
if self.flip:
ver[len(sel) - i - 1].select = True
else:
ver[i].select = True
elif self.select_type == 'EDGE':
bpy.context.tool_settings.mesh_select_mode = [False, True, False]
ver = obj.data.vertices
edg = obj.data.edges
sel = []
for e in edg:
d = ver[e.vertices[0]].co - ver[e.vertices[1]].co
sel.append((d.length, e.index))
sel.sort(reverse=self.flip)
bpy.ops.object.mode_set()
valor = round(len(sel) / 100 * self.indice)
if self.delta:
for i in range(len(sel[:valor])):
edg[sel[i][1]].select = True
else:
for i in range(len(sel[:valor])):
if self.flip:
edg[len(sel) - i - 1].select = True
else:
edg[i].select = True
elif self.select_type == 'FACE':
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
fac = obj.data.polygons
sel = []
for f in fac:
sel.append((f.area, f.index))
sel.sort(reverse=self.flip)
bpy.ops.object.mode_set()
valor = round(len(sel) / 100 * self.indice)
if self.delta:
for i in range(len(sel[:valor])):
fac[sel[i][1]].select = True
else:
for i in range(len(sel[:valor])):
if self.flip:
fac[len(sel) - i - 1].select = True
else:
fac[i].select = True
bpy.ops.object.mode_set(mode='EDIT')
return {'FINISHED'}
def register():
bpy.utils.register_class(SelVertEdgeFace)
def unregister():
bpy.utils.register_class(SelVertEdgeFace)
if __name__ == '__main__':
register()

View File

@ -1,111 +0,0 @@
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# By CoDEmanX
# updated by lijenstina
import bpy
import bmesh
from bpy.types import Panel
import time
# Define Globals
STORE_COUNT = (0, 0, 0) # Store the previous count
TIMER_STORE = 1 # Store the time.time floats
def check_the_obj_polycount(context, delay=0.0):
global STORE_COUNT
global TIMER_STORE
info_str = ""
tris = quads = ngons = 0
try:
# it's weak sauce but this will in certain cases run many times a second
if TIMER_STORE == 1 or delay == 0 or time.time() > TIMER_STORE + delay:
ob = context.active_object
if ob.mode == 'EDIT':
me = ob.data
bm = bmesh.from_edit_mesh(me)
for f in bm.faces:
v = len(f.verts)
if v == 3:
tris += 1
elif v == 4:
quads += 1
else:
ngons += 1
bmesh.update_edit_mesh(me)
else:
for p in ob.data.polygons:
count = p.loop_total
if count == 3:
tris += 1
elif count == 4:
quads += 1
else:
ngons += 1
STORE_COUNT = (ngons, quads, tris)
info_str = " Ngons: %i Quads: %i Tris: %i" % (ngons, quads, tris)
TIMER_STORE = time.time()
else:
info_str = " Ngons: %i Quads: %i Tris: %i" % STORE_COUNT
except:
info_str = " Polygon info could not be retrieved"
return info_str
class DATA_PT_info_panel(Panel):
"""Creates a face info / select panel in the Object properties window"""
bl_label = "Face Info / Select"
bl_idname = "DATA_PT_face_info"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "data"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(self, context):
return (context.active_object is not None and
context.active_object.type == 'MESH')
def draw(self, context):
layout = self.layout
mesh_extra_tools = context.scene.mesh_extra_tools
check_used = mesh_extra_tools.mesh_info_show
check_delay = mesh_extra_tools.mesh_info_delay
info_str = ""
box = layout.box()
col = box.column()
split = col.split(factor=0.6 if check_used else 0.75, align=True)
split.prop(mesh_extra_tools, "mesh_info_show", toggle=True)
split.prop(mesh_extra_tools, "mesh_info_delay")
if check_used:
info_str = check_the_obj_polycount(context, check_delay)
col.label(text=info_str, icon='MESH_DATA')
col = layout.column()
col.label(text="Select faces by type:")
row = layout.row()
row.operator("data.facetype_select", text="Ngons").face_type = "5"
row.operator("data.facetype_select", text="Quads").face_type = "4"
row.operator("data.facetype_select", text="Tris").face_type = "3"

View File

@ -1,208 +0,0 @@
# Copyright (C) 2011, Dolf Veenvliet
# Extrude a selection from a mesh multiple times
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
"""
Usage:
Select all items whose normals face a certain direction
Additional links:
Author Site: http://www.macouno.com
e-mail: dolf {at} macouno {dot} com
"""
import bpy
from bpy.types import Operator
from mathutils import Vector
from math import radians
from bpy.props import (
FloatVectorProperty,
FloatProperty,
BoolProperty,
EnumProperty,
)
class Select_by_direction():
# Initialise the class
def __init__(self, context, direction, divergence, extend, space):
self.ob = context.active_object
bpy.ops.object.mode_set(mode='OBJECT')
self.space = space
# if we do stuff in global space we need to object matrix
if self.space == 'GLO':
# TODO: not sure if this is the correct way to solve the crash - lijenstina
mat = self.ob.matrix_world
mat_rot = mat.to_3x3().inverted()
direction = mat_rot * Vector(direction)
else:
direction = Vector(direction)
direction = direction.normalized()
vertSelect = bpy.context.tool_settings.mesh_select_mode[0]
edgeSelect = bpy.context.tool_settings.mesh_select_mode[1]
faceSelect = bpy.context.tool_settings.mesh_select_mode[2]
if Vector(direction).length:
# Vert select
if vertSelect:
hasSelected = self.hasSelected(self.ob.data.vertices)
for v in self.ob.data.vertices:
normal = v.normal
s = self.selectCheck(v.select, hasSelected, extend)
d = self.deselectCheck(v.select, hasSelected, extend)
if s or d:
angle = direction.angle(normal)
# Check if the verts match any of the directions
if s and angle <= divergence:
v.select = True
if d and angle > divergence:
v.select = False
# Edge select
if edgeSelect:
hasSelected = self.hasSelected(self.ob.data.edges)
for e in self.ob.data.edges:
s = self.selectCheck(e.select, hasSelected, extend)
d = self.deselectCheck(e.select, hasSelected, extend)
# Check if the edges match any of the directions
if s or d:
normal = self.ob.data.vertices[e.vertices[0]].normal
normal += self.ob.data.vertices[e.vertices[1]].normal
angle = direction.angle(normal)
if s and angle <= divergence:
e.select = True
if d and angle > divergence:
e.select = False
# Face select
if faceSelect:
hasSelected = self.hasSelected(self.ob.data.polygons)
# Loop through all the given faces
for f in self.ob.data.polygons:
s = self.selectCheck(f.select, hasSelected, extend)
d = self.deselectCheck(f.select, hasSelected, extend)
if s or d:
angle = direction.angle(f.normal)
# Check if the faces match any of the directions
if s and angle <= divergence:
f.select = True
if d and angle > divergence:
f.select = False
bpy.ops.object.mode_set(mode='EDIT')
# See if the current item should be selected or not
def selectCheck(self, isSelected, hasSelected, extend):
# If the current item is not selected we may want to select
if not isSelected:
# If we are extending or nothing is selected we want to select
if extend or not hasSelected:
return True
return False
# See if the current item should be deselected or not
def deselectCheck(self, isSelected, hasSelected, extend):
# If the current item is selected we may want to deselect
if isSelected:
# If something is selected and we're not extending we want to deselect
if hasSelected and not extend:
return True
return False
# See if there is at least one selected item
def hasSelected(self, items):
for item in items:
if item.select:
return True
return False
class Select_init(Operator):
bl_idname = "mesh.select_by_direction"
bl_label = "Select by direction"
bl_description = ("Select all items with normals facing a certain direction,\n"
"defined by a vector with coordinates X, Y, Z")
bl_options = {'REGISTER', 'UNDO'}
direction: FloatVectorProperty(
name="Direction",
description="Define a vector from the inputs axis X, Y, Z\n"
"Used to define the normals direction",
default=(0.0, 0.0, 1.0),
min=-100.0, max=100.0,
soft_min=-10.0, soft_max=10.0,
step=100,
precision=2
)
divergence: FloatProperty(
name="Divergence",
description="The number of degrees the selection may differ from the Vector\n"
"(Input is converted to radians)",
default=radians(30.0),
min=0.0, max=radians(360.0),
soft_min=0.0, soft_max=radians(360.0),
step=radians(5000),
precision=2,
subtype='ANGLE'
)
extend: BoolProperty(
name="Extend",
description="Extend the current selection",
default=False
)
# The spaces we use
spaces = (('LOC', 'Local', ''), ('GLO', 'Global', ''))
space: EnumProperty(
items=spaces,
name="Space",
description="The space to interpret the directions in",
default='LOC'
)
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'MESH')
def execute(self, context):
Select_by_direction(context, self.direction, self.divergence, self.extend, self.space)
return {'FINISHED'}

View File

@ -1,234 +0,0 @@
# mesh_select_by_edge_length.py Copyright (C) 2011, Dolf Veenvliet
# Extrude a selection from a mesh multiple times
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
"""
Usage:
Launch from from "Select -> By edge length"
Select all items whose scale/length/surface matches a certain edge length
Additional links:
Author Site: http://www.macouno.com
e-mail: dolf {at} macouno {dot} com
"""
import bpy
from bpy.props import (
FloatProperty,
BoolProperty,
EnumProperty,
)
class Select_by_edge_length():
# Initialize the class
def __init__(self, context, edgeLength, edgeSize, extend, space, start_new):
if start_new:
bpy.ops.mesh.select_all(action='DESELECT')
self.ob = context.active_object
bpy.ops.object.mode_set(mode='OBJECT')
self.space = space
self.obMat = self.ob.matrix_world
bigger = (True if edgeSize == 'BIG' else False)
smaller = (True if edgeSize == 'SMALL' else False)
# We ignore vert selections completely
edgeSelect = bpy.context.tool_settings.mesh_select_mode[1]
faceSelect = bpy.context.tool_settings.mesh_select_mode[2]
# Edge select
if edgeSelect:
hasSelected = self.hasSelected(self.ob.data.edges)
for e in self.ob.data.edges:
if self.selectCheck(e.select, hasSelected, extend):
lene = self.getEdgeLength(e.vertices)
if (lene == edgeLength or (bigger and lene >= edgeLength) or
(smaller and lene <= edgeLength)):
e.select = True
if self.deselectCheck(e.select, hasSelected, extend):
lene = self.getEdgeLength(e.vertices)
if (lene != edgeLength and not (bigger and lene >= edgeLength) and
not (smaller and lene <= edgeLength)):
e.select = False
# Face select
if faceSelect:
hasSelected = self.hasSelected(self.ob.data.polygons)
# Loop through all the given faces
for f in self.ob.data.polygons:
# Check if the faces match any of the directions
if self.selectCheck(f.select, hasSelected, extend):
mine, maxe = 0.0, 0.0
for i, e in enumerate(f.edge_keys):
lene = self.getEdgeLength(e)
if not i:
mine = lene
maxe = lene
elif lene < mine:
mine = lene
elif lene > maxe:
maxe = lene
if ((mine == edgeLength and maxe == edgeLength) or
(bigger and mine >= edgeLength) or
(smaller and maxe <= edgeLength)):
f.select = True
if self.deselectCheck(f.select, hasSelected, extend):
mine, maxe = 0.0, 0.0
for i, e in enumerate(f.edge_keys):
lene = self.getEdgeLength(e)
if not i:
mine = lene
maxe = lene
elif lene < mine:
mine = lene
elif lene > maxe:
maxe = lene
if ((mine != edgeLength and maxe != edgeLength) and
not (bigger and mine >= edgeLength) and
not (smaller and maxe <= edgeLength)):
f.select = False
bpy.ops.object.mode_set(mode='EDIT')
# Get the length of an edge, by giving this function all verts (2) in the edge
def getEdgeLength(self, verts):
vec1 = self.ob.data.vertices[verts[0]].co
vec2 = self.ob.data.vertices[verts[1]].co
vec = vec1 - vec2
if self.space == 'GLO':
vec = self.obMat * vec
return round(vec.length, 5)
# See if the current item should be selected or not
def selectCheck(self, isSelected, hasSelected, extend):
# If the current item is not selected we may want to select
if not isSelected:
# If we are extending or nothing is selected we want to select
if extend or not hasSelected:
return True
return False
# See if the current item should be deselected or not
def deselectCheck(self, isSelected, hasSelected, extend):
# If the current item is selected we may want to deselect
if isSelected:
# If something is selected and we're not extending we want to deselect
if hasSelected and not extend:
return True
return False
# See if there is at least one selected item
def hasSelected(self, items):
for item in items:
if item.select:
return True
return False
class Select_init(bpy.types.Operator):
bl_idname = "mesh.select_by_edge_length"
bl_label = "Select by edge length"
bl_description = ("Select all items whose scale/length/surface matches a certain edge length \n"
"Does not work in Vertex Select mode")
bl_options = {'REGISTER', 'UNDO'}
edgeLength: FloatProperty(
name="Edge length",
description="The comparison scale in Blender units",
default=1.0,
min=0.0, max=1000.0,
soft_min=0.0, soft_max=100.0,
step=100,
precision=2
)
# Changed to Enum as two separate Booleans didn't make much sense
sizes = (('SMALL', 'Smaller', "Select items smaller or equal the size setting"),
('BIG', 'Bigger', "Select items bigger or equal to the size setting"),
('EQUAL', 'Equal', "Select edges equal to the size setting"))
edgeSize: EnumProperty(
items=sizes,
name="Edge comparison",
description="Choose the relation to set edge length",
default='EQUAL'
)
extend: BoolProperty(
name="Extend",
description="Extend the current selection",
default=False
)
start_new: BoolProperty(
name="Fresh Start",
default=False,
description="Start from no previous selection"
)
# The spaces we use
spaces = (('LOC', 'Local', "Use Local space"),
('GLO', 'Global', "Use Global Space"))
space: EnumProperty(
items=spaces,
name="Space",
description="The space to interpret the directions in",
default='LOC'
)
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'MESH' and not bpy.context.tool_settings.mesh_select_mode[0])
def execute(self, context):
Select_by_edge_length(context, self.edgeLength, self.edgeSize,
self.extend, self.space, self.start_new)
return {'FINISHED'}

View File

@ -1,196 +0,0 @@
# mesh_select_by_pi.py Copyright (C) 2011, Dolf Veenvliet
# Extrude a selection from a mesh multiple times
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
"""
Usage:
Select fake random based on pi
Additional links:
Author Site: http://www.macouno.com
e-mail: dolf {at} macouno {dot} com
"""
import bpy
from bpy.types import Operator
from bpy.props import BoolProperty
class Select_by_pi():
# Initialise the class
def __init__(self, context, e, invert, extend, start_new):
self.ob = context.active_object
# keep or not the original selection (helps with selected all)
if start_new:
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
self.invert = invert
# Make pi as a list of integers
if e:
self.pi = list('27182818284590452353602874713526624977572470936999')
else:
self.pi = list('31415926535897932384626433832795028841971693993751')
self.piLen = len(self.pi)
self.piPos = 0
vertSelect = bpy.context.tool_settings.mesh_select_mode[0]
edgeSelect = bpy.context.tool_settings.mesh_select_mode[1]
faceSelect = bpy.context.tool_settings.mesh_select_mode[2]
# Vert select
if vertSelect:
hasSelected = self.hasSelected(self.ob.data.vertices)
for v in self.ob.data.vertices:
s = self.selectCheck(v.select, hasSelected, extend)
d = self.deselectCheck(v.select, hasSelected, extend)
# Check if the verts match any of the directions
if s and self.choose():
v.select = True
if d and not self.choose():
v.select = False
# Edge select
if edgeSelect:
hasSelected = self.hasSelected(self.ob.data.edges)
for e in self.ob.data.edges:
s = self.selectCheck(e.select, hasSelected, extend)
d = self.deselectCheck(e.select, hasSelected, extend)
if s and self.choose():
e.select = True
if d and not self.choose():
e.select = False
# Face select
if faceSelect:
hasSelected = self.hasSelected(self.ob.data.polygons)
# Loop through all the given faces
for f in self.ob.data.polygons:
s = self.selectCheck(f.select, hasSelected, extend)
d = self.deselectCheck(f.select, hasSelected, extend)
# Check if the faces match any of the directions
if s and self.choose():
f.select = True
if d and not self.choose():
f.select = False
bpy.ops.object.mode_set(mode='EDIT')
# Choose by pi
def choose(self):
choice = True
# We just choose the odd numbers
if int(self.pi[self.piPos]) % 2:
choice = False
if self.invert:
choice = not choice
self.incrementPiPos()
return choice
# Increment the pi position
def incrementPiPos(self):
self.piPos += 1
if self.piPos == self.piLen:
self.piPos = 0
# See if the current item should be selected or not
def selectCheck(self, isSelected, hasSelected, extend):
# If the current item is not selected we may want to select
if not isSelected:
# If we are extending or nothing is selected we want to select
if extend or not hasSelected:
return True
return False
# See if the current item should be deselected or not
def deselectCheck(self, isSelected, hasSelected, extend):
# If the current item is selected we may want to deselect
if isSelected:
# If something is selected and we're not extending we want to deselect
if hasSelected and not extend:
return True
return False
# See if there is at least one selected item
def hasSelected(self, items):
for item in items:
if item.select:
return True
return False
class Select_init(Operator):
bl_idname = "mesh.select_by_pi"
bl_label = "Select by Pi or e"
bl_description = ("Select Vertices/Edges/Faces based on pi or e for a random-like selection\n"
"Number Pi (3.14 etc.) or e (2.71828 - Euler's number)")
bl_options = {'REGISTER', 'UNDO'}
e: BoolProperty(
name="Use e",
description="Use e as the base of selection instead of pi",
default=False
)
invert: BoolProperty(
name="Invert",
description="Invert the selection result",
default=False
)
extend: BoolProperty(
name="Extend",
description="Extend the current selection",
default=False
)
start_new: BoolProperty(
name="Fresh Start",
default=False,
description="Start from no previous selection"
)
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'MESH')
def execute(self, context):
Select_by_pi(context, self.e, self.invert, self.extend, self.start_new)
return {'FINISHED'}

View File

@ -1,75 +0,0 @@
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# By CoDEmanX
import bpy
from bpy.types import Operator
from bpy.props import (
EnumProperty,
BoolProperty,
)
class DATA_OP_facetype_select(Operator):
bl_idname = "data.facetype_select"
bl_label = "Select by face type"
bl_description = "Select all faces of a certain type"
bl_options = {'REGISTER', 'UNDO'}
face_type: EnumProperty(
name="Select faces:",
items=(("3", "Triangles", "Faces made up of 3 vertices"),
("4", "Quads", "Faces made up of 4 vertices"),
("5", "Ngons", "Faces made up of 5 and more vertices")),
default="5"
)
extend: BoolProperty(
name="Extend",
description="Extend Selection",
default=False
)
@classmethod
def poll(cls, context):
return context.active_object is not None and context.active_object.type == 'MESH'
def execute(self, context):
try:
bpy.ops.object.mode_set(mode='EDIT')
if not self.extend:
bpy.ops.mesh.select_all(action='DESELECT')
context.tool_settings.mesh_select_mode = (False, False, True)
if self.face_type == "3":
bpy.ops.mesh.select_face_by_sides(number=3, type='EQUAL')
elif self.face_type == "4":
bpy.ops.mesh.select_face_by_sides(number=4, type='EQUAL')
else:
bpy.ops.mesh.select_face_by_sides(number=4, type='GREATER')
return {'FINISHED'}
except Exception as e:
print("\n[Select by face type]\nOperator: data.facetype_select\nERROR: %s\n" % e)
self.report({'WARNING'},
"Face selection could not be performed (Check the console for more info)")
return {'CANCELLED'}

View File

@ -1,134 +0,0 @@
# Copyright (C) 2011, Dolf Veenvliet
# Extrude a selection from a mesh multiple times
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
# ##### END GPL LICENSE BLOCK #####
"""
Usage:
Launch from from "Select -> Connected faces"
Additional links:
Author Site: http://www.macouno.com
e-mail: dolf {at} macouno {dot} com
"""
import bpy
from bpy.types import Operator
from bpy.props import (
IntProperty,
BoolProperty,
)
class Select_connected_faces():
# Initialize the class
def __init__(self, context, iterations, extend):
self.ob = context.active_object
bpy.ops.object.mode_set(mode='OBJECT')
# Make a list of all selected vertices
selVerts = [v.index for v in self.ob.data.vertices if v.select]
hasSelected = self.hasSelected(self.ob.data.polygons)
for i in range(iterations):
nextVerts = []
for f in self.ob.data.polygons:
if self.selectCheck(f.select, hasSelected, extend):
for v in f.vertices:
if v in selVerts:
f.select = True
if f.select:
for v in f.vertices:
if v not in selVerts:
nextVerts.append(v)
elif self.deselectCheck(f.select, hasSelected, extend):
for v in f.vertices:
if v in selVerts:
f.select = False
selVerts = nextVerts
bpy.ops.object.mode_set(mode='EDIT')
# See if the current item should be selected or not
def selectCheck(self, isSelected, hasSelected, extend):
# If the current item is not selected we may want to select
if not isSelected:
return True
return False
# See if the current item should be deselected or not
def deselectCheck(self, isSelected, hasSelected, extend):
# If the current item is selected we may want to deselect
if isSelected:
# If something is selected and we're not extending we want to deselect
if hasSelected and not extend:
return True
return False
# See if there is at least one selected item
def hasSelected(self, items):
for item in items:
if item.select:
return True
return False
class Select_init(Operator):
bl_idname = "mesh.select_connected_faces"
bl_label = "Select connected faces"
bl_description = ("Select all faces connected to the current selection \n"
"Works only in Face Selection mode")
bl_options = {'REGISTER', 'UNDO'}
# Iterations
iterations: IntProperty(
name="Iterations",
description="Run the selection the given number of times",
default=1,
min=0, max=300,
soft_min=0, soft_max=100
)
extend: BoolProperty(
name="Extend",
description="Extend the current selection",
default=False
)
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'MESH' and
bpy.context.tool_settings.mesh_select_mode[0] is False and
bpy.context.tool_settings.mesh_select_mode[1] is False and
bpy.context.tool_settings.mesh_select_mode[2] is True)
def execute(self, context):
Select_connected_faces(context, self.iterations, self.extend)
return {'FINISHED'}

View File

@ -1,632 +0,0 @@
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Topokit 2",
"author": "dustractor",
"version": (2, 0),
"blender": (2, 60, 0),
"location": "Edit mesh > Vertices/ Edges/ Faces menus",
"description": "",
"warning": "",
"wiki_url": "",
"category": "Mesh"}
import bpy
from bpy.types import Operator
# In between calls, this stores any data that is expensive or static,
# matched to the size of the mesh and the id of the operator that created it
cachedata = dict()
# tkey is moved to mesh_extra_tools\__init__.py register function
# just a mix-in for the operators...
class meshpoller:
@classmethod
def poll(self, context):
try:
assert context.active_object.type == "MESH"
except:
return False
finally:
return True
# BEGIN VERTICES SECTION
# This one works similarly to normal 'grow' (ctrl + NUMPAD_PLUS),
# except the original selection is not part of the result,
#
# 0--0--0 0--1--0
# | | | | | |
# 0--1--0 --> 1--0--1
# | | | | | |
# 0--0--0 0--1--0
class MESH_OT_vneighbors_edgewise(meshpoller, Operator):
bl_idname = "mesh.v2v_by_edge"
bl_label = "Neighbors by Edge"
bl_description = ("Select neighbour vertices of a starting selected vertex\n"
"Similar to Grow Selection - apart from the\n"
"original selection is not part of the result")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global cachedata
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
mesh = obj.data
meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self))
next_state = bytearray(meshkey[0])
if (meshkey == obj.tkkey) and (meshkey in cachedata):
vert_to_vert_map, prev_state = cachedata[meshkey]
else:
vert_to_vert_map = {i: {} for i in range(meshkey[0])}
for a, b in mesh.edge_keys:
vert_to_vert_map[a][b] = 1
vert_to_vert_map[b][a] = 1
obj.tkkey = meshkey
prev_state = None
if not prev_state:
selected_vert_indices = filter(
lambda _: mesh.vertices[_].select,
range(len(mesh.vertices))
)
else:
selected_vert_indices = filter(
lambda _: mesh.vertices[_].select and not prev_state[_],
range(len(mesh.vertices))
)
for v in selected_vert_indices:
for neighbor_index in vert_to_vert_map[v]:
next_state[neighbor_index] = True
mesh.vertices.foreach_set("select", next_state)
cachedata[meshkey] = (vert_to_vert_map, next_state)
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
# This one is an alternate / counterpart to the previous.
# Think: diagonal opposite corners of a quad
# NOTE: does not apply to a triangle, since verts have no "opposite"
#
# 0--0--0 1--0--1
# | | | | | |
# 0--1--0 --> 0--0--0
# | | | | | |
# 0--0--0 1--0--1
class MESH_OT_vneighbors_facewise(meshpoller, Operator):
bl_idname = "mesh.v2v_facewise"
bl_label = "Neighbors by Face - Edge"
bl_description = ("Select diagonal opposite vertices of neighbour quads\n"
"Does not work with triangles\n"
"The original selection is not part of the result")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global cachedata
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
mesh = obj.data
meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self))
next_state = bytearray(meshkey[0])
if (meshkey == obj.tkkey) and (meshkey in cachedata):
vert_to_vert_map = cachedata[meshkey]
else:
vert_to_vert_map = {i: {} for i in range(meshkey[0])}
for a, b in mesh.edge_keys:
vert_to_vert_map[a][b] = 1
vert_to_vert_map[b][a] = 1
obj.tkkey = meshkey
faces = filter(lambda face: (len(face.vertices) == 4) and
(face.select is False), mesh.polygons)
for f in faces:
has = False
t = set()
for v in f.vertices:
if mesh.vertices[v].select:
has = True
t.update(vert_to_vert_map[v])
if has:
for v in f.vertices:
if not mesh.vertices[v].select:
if v not in t:
next_state[v] = 1
mesh.vertices.foreach_set("select", next_state)
cachedata[meshkey] = vert_to_vert_map
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
# END VERTICES SECTION
# BEGIN EDGES SECTION
# +--0--+--0--+--0--+ +--0--+--0--+--0--+
# | | | | | | | |
# 0 0 0 0 0 1 1 0
# | | | | | | | |
# +--0--+--1--+--0--+ ---> +--0--+--0--+--0--+
# | | | | | | | |
# 0 0 0 0 0 1 1 0
# | | | | | | | |
# +--0--+--0--+--0--+ +--0--+--0--+--0--+
class MESH_OT_eneighbors_shared_v_f(meshpoller, Operator):
bl_idname = "mesh.e2e_evfe"
bl_label = "Neighbors by Vert and Face"
bl_description = ("Select edges that share the neighbour vertices and faces\n"
"of the starting selected edge\n"
"The original selection is not part of the result")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global cachedata
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
mesh = obj.data
meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self))
state_mask = bytearray(meshkey[1])
if (meshkey == obj.tkkey) and (meshkey in cachedata):
edge_to_edges_dict = cachedata
else:
edge_key_to_index = {k: i for i, k in enumerate(mesh.edge_keys)}
edge_to_edges_dict = {i: set() for i in range(len(mesh.edges))}
for f in mesh.polygons:
fed = [edge_key_to_index[k] for k in f.edge_keys]
for k in f.edge_keys:
edge_to_edges_dict[edge_key_to_index[k]].update(fed)
obj.tkkey = meshkey
for e in filter(lambda _: mesh.edges[_].select, edge_to_edges_dict):
k1 = set(mesh.edges[e].key)
for n in edge_to_edges_dict[e]:
k2 = set(mesh.edges[n].key)
if not k1.isdisjoint(k2):
state_mask[n] = True
for e in mesh.edges:
e.select ^= state_mask[e.index]
cachedata[meshkey] = edge_key_to_index
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
# +--0--+--0--+--0--+ +--0--+--0--+--0--+
# | | | | | | | |
# 0 0 0 0 0 1 1 0
# | | | | | | | |
# +--0--+--1--+--0--+ ---> +--1--+--0--+--1--+
# | | | | | | | |
# 0 0 0 0 0 1 1 0
# | | | | | | | |
# +--0--+--0--+--0--+ +--0--+--0--+--0--+
class MESH_OT_eneighbors_shared_v(meshpoller, Operator):
bl_idname = "mesh.e2e_eve"
bl_label = "Neighbors by Vert"
bl_description = ("Select edges that share the neighbour vertices\n"
"of the starting selected edge\n"
"The original selection is not part of the result")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bpy.ops.object.mode_set(mode="OBJECT")
mesh = context.active_object.data
state_mask = bytearray(len(mesh.edges))
for e in mesh.edges:
state_mask[e.index] = \
mesh.vertices[e.vertices[0]].select ^ mesh.vertices[e.vertices[1]].select
mesh.edges.foreach_set('select', state_mask)
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
# +--0--+--0--+--0--+ +--0--+--1--+--0--+
# | | | | | | | |
# 0 0 0 0 0 1 1 0
# | | | | | | | |
# +--0--+--1--+--0--+ ---> +--0--+--0--+--0--+
# | | | | | | | |
# 0 0 0 0 0 1 1 0
# | | | | | | | |
# +--0--+--0--+--0--+ +--0--+--1--+--0--+
class MESH_OT_eneighbors_shared_f(meshpoller, Operator):
bl_idname = "mesh.e2e_efe"
bl_label = "Neighbors by Face"
bl_description = ("Select edges of neighbour faces to the starting selected edge\n"
"The original selection is not part of the result")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global cachedata
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
mesh = obj.data
meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self))
if (meshkey == obj.tkkey) and (meshkey in cachedata):
edge_to_edges_dict = cachedata
else:
edge_key_to_index = {k: i for i, k in enumerate(mesh.edge_keys)}
edge_to_edges_dict = {i: set() for i in range(len(mesh.edges))}
for f in mesh.polygons:
fed = [edge_key_to_index[k] for k in f.edge_keys]
for k in f.edge_keys:
edge_to_edges_dict[edge_key_to_index[k]].update(fed)
obj.tkkey = meshkey
state_mask, esel = (bytearray(meshkey[1]), bytearray(meshkey[1]))
mesh.edges.foreach_get('select', esel)
for e in filter(lambda _: mesh.edges[_].select, range(meshkey[1])):
for n in edge_to_edges_dict[e]:
state_mask[n] = 1
for e in range(meshkey[1]):
esel[e] ^= state_mask[e]
mesh.edges.foreach_set('select', esel)
cachedata[meshkey] = edge_to_edges_dict
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
# Notice that on these next two, the original selection stays
# +--0--+--0--+--0--+ +--0--+--1--+--0--+
# | | | | | | | |
# 0 0 0 0 0 0 0 0
# | | | | | | | |
# +--0--+--1--+--0--+ ---> +--0--+--1--+--0--+
# | | | | | | | |
# 0 0 0 0 0 0 0 0
# | | | | | | | |
# +--0--+--0--+--0--+ +--0--+--1--+--0--+
class MESH_OT_eneighbors_shared_f_notv(meshpoller, Operator):
bl_idname = "mesh.e2e_efnve"
bl_label = "Lateral Neighbors"
bl_description = ("Select edges that are lateral neighbours\n"
"The original selection is included in the result")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global cachedata
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
mesh = obj.data
meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self))
state_mask = bytearray(meshkey[1])
if (meshkey == obj.tkkey) and (meshkey in cachedata):
edge_to_face_map, edge_key_to_index = cachedata[meshkey]
else:
edge_key_to_index = {}
edge_to_face_map = {i: set() for i in range(meshkey[1])}
for i, k in enumerate(mesh. edge_keys):
edge_key_to_index[k] = i
for f in mesh.polygons:
for k in f.edge_keys:
edge_to_face_map[edge_key_to_index[k]].add(f.index)
obj.tkkey = meshkey
selected_edge_indices = filter(lambda _: mesh.edges[_].select, range(meshkey[1]))
for e in selected_edge_indices:
for f in edge_to_face_map[e]:
for k in mesh.polygons[f].edge_keys:
hasv_in = False
for v in mesh.edges[e].key:
if v in k:
hasv_in = True
if hasv_in:
continue
else:
state_mask[edge_key_to_index[k]] = True
for e in filter(lambda _: state_mask[_], range(meshkey[1])):
mesh.edges[e].select |= state_mask[e]
cachedata[meshkey] = (edge_to_face_map, edge_key_to_index)
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
# +--0--+--0--+--0--+ +--0--+--0--+--0--+
# | | | | | | | |
# 0 0 0 0 0 0 0 0
# | | | | | | | |
# +--0--+--1--+--0--+ ---> +--1--+--1--+--1--+
# | | | | | | | |
# 0 0 0 0 0 0 0 0
# | | | | | | | |
# +--0--+--0--+--0--+ +--0--+--0--+--0--+
class MESH_OT_eneighbors_shared_v_notf(meshpoller, Operator):
bl_idname = "mesh.e2e_evnfe"
bl_label = "Longitudinal Edges"
bl_description = ("Select Edges along the same longitude of the starting edge\n"
"The original selection is included in the result")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global cachedata
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
mesh = obj.data
meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self))
state_mask = bytearray(meshkey[1])
vstate = bytearray(meshkey[0])
mesh.vertices.foreach_get('select', vstate)
if (meshkey == obj.tkkey) and (meshkey in cachedata):
edge_to_face_map, vert_to_vert_map, edge_key_to_index = cachedata[meshkey]
else:
edge_key_to_index = {}
vert_to_vert_map = {i: set() for i in range(meshkey[0])}
edge_to_face_map = {i: set() for i in range(meshkey[1])}
for i, k in enumerate(mesh.edge_keys):
edge_key_to_index[k] = i
vert_to_vert_map[k[0]].add(k[1])
vert_to_vert_map[k[1]].add(k[0])
for f in mesh.polygons:
for k in f.edge_keys:
edge_to_face_map[edge_key_to_index[k]].add(f.index)
obj.tkkey = meshkey
selected_edge_indices = filter(lambda _: mesh.edges[_].select, range(meshkey[1]))
for e in selected_edge_indices:
for v in mesh.edges[e].key:
state_mask[v] ^= 1
for f in edge_to_face_map[e]:
for v in mesh.polygons[f].vertices:
vstate[v] = 1
for v in filter(lambda _: state_mask[_], range(meshkey[1])):
for n in vert_to_vert_map[v]:
if not vstate[n] and (n != v):
mesh.edges[edge_key_to_index[(min(v, n), max(v, n))]].select = True
cachedata[meshkey] = (edge_to_face_map, vert_to_vert_map, edge_key_to_index)
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
# Deselects edges which are at the edge of a face-selection,
# causing selection to 'shrink in'
class MESH_OT_inner_edges(meshpoller, Operator):
bl_idname = "mesh.ie"
bl_label = "Inner Edge Selection"
bl_description = ("Deselects edges which are at the border\n"
"of a starting face selection\n"
"causing the selection to shrink inwards")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global cachedata
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
mesh = obj.data
meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self))
state_mask = bytearray(meshkey[1])
if (meshkey == obj.tkkey) and (meshkey in cachedata):
edge_to_face_map = cachedata[meshkey]
else:
edge_key_to_index = {k: i for i, k in enumerate(mesh.edge_keys)}
edge_to_face_map = {i: set() for i in range(meshkey[1])}
for f in mesh.polygons:
for k in f.edge_keys:
edge_to_face_map[edge_key_to_index[k]].add(f.index)
obj.tkkey = meshkey
for e in filter(lambda _: mesh.edges[_].select, range(meshkey[1])):
for f in edge_to_face_map[e]:
if mesh.polygons[f].select:
state_mask[e] ^= 1
for e in range(meshkey[1]):
mesh.edges[e].select ^= state_mask[e]
cachedata[meshkey] = edge_to_face_map
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
# END EDGES SECTION
# BEGIN FACES SECTION
# here is another one which functions very similarly to the ctrl+NUMPAD_PLUS 'growth'
# but it deselects the original selection, of course.
# This would be your checkerboard-type growth.
# [0][0][0] [0][1][0]
# [0][1][0] ---> [1][0][1]
# [0][0][0] [0][1][0]
class MESH_OT_fneighbors_shared_e(meshpoller, Operator):
bl_idname = "mesh.f2f_fef"
bl_label = "Neighbor Faces sharing an Edge"
bl_description = ("Selects faces that share an edge with the starting face selection\n"
"Similar to the Grow selection \n"
"The original selection is not part of the result")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global cachedata
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
mesh = obj.data
meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self))
if (meshkey == obj.tkkey) and (meshkey in cachedata):
face_to_face_map = cachedata[meshkey]
else:
edge_key_to_index = {k: i for i, k in enumerate(mesh.edge_keys)}
edge_to_face_map = {i: set() for i in range(meshkey[1])}
for f in mesh.polygons:
for k in f.edge_keys:
edge_to_face_map[edge_key_to_index[k]].add(f.index)
face_to_face_map = {i: set() for i in range(meshkey[2])}
for f in mesh.polygons:
for k in f.edge_keys:
face_to_face_map[f.index].update(edge_to_face_map[edge_key_to_index[k]])
obj.tkkey = meshkey
mask_state = bytearray(meshkey[2])
for f in filter(lambda _: mesh.polygons[_].select, range(meshkey[2])):
for n in face_to_face_map[f]:
mask_state[n] = True
for f in range(meshkey[2]):
mesh.polygons[f].select ^= mask_state[f]
cachedata[meshkey] = face_to_face_map
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
# [0][0][0] [1][0][1]
# [0][1][0] ---> [0][0][0]
# [0][0][0] [1][0][1]
class MESH_OT_fneighbors_shared_v_note(meshpoller, Operator):
bl_idname = "mesh.f2f_fvnef"
bl_label = "Neighbors by Vertex not Edge"
bl_description = ("Select neighbour faces that share a vertex\n"
"with the starting selection\n"
"The original selection is not part of the result")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global cachedata
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
mesh = obj.data
meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self))
if (meshkey == obj.tkkey) and (meshkey in cachedata):
edge_key_to_index = cachedata[meshkey]
else:
edge_key_to_index = {k: i for i, k in enumerate(mesh.edge_keys)}
obj.tkkey = meshkey
state_mask = bytearray(meshkey[2])
face_verts = set()
for f in filter(lambda _: mesh.polygons[_].select, range(meshkey[2])):
face_verts.update(mesh.polygons[f].vertices)
for f in filter(lambda _: not mesh.polygons[_].select, range(meshkey[2])):
ct = 0
for v in mesh.polygons[f].vertices:
ct += (v in face_verts)
if ct == 1:
state_mask[f] = 1
mesh.polygons.foreach_set('select', state_mask)
cachedata[meshkey] = edge_key_to_index
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
# https://en.wikipedia.org/wiki/Conway's_Game_of_Life
class MESH_OT_conway(meshpoller, Operator):
bl_idname = "mesh.conway"
bl_label = "Conway's Selection"
bl_description = ("Select Faces with the Conway's game of life algorithm\n"
"Requires an initial Face selection\n"
"The edges of the original selection are included in the result")
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
global cachedata
bpy.ops.object.mode_set(mode="OBJECT")
obj = context.active_object
mesh = obj.data
meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self))
if (meshkey == obj.tkkey) and (meshkey in cachedata):
vert_to_face_map = cachedata[meshkey]
else:
vert_to_face_map = {i: set() for i in range(meshkey[0])}
for f in mesh.polygons:
for v in f.vertices:
vert_to_face_map[v].add(f.index)
obj.tkkey = meshkey
sel = set()
uns = set()
F = {i: set() for i in range(meshkey[2])}
for f in range(meshkey[2]):
for v in mesh.polygons[f].vertices:
for n in filter(lambda _: mesh.polygons[_].select and (_ != f), vert_to_face_map[v]):
F[f].add(n)
for f in F:
if len(F[f]) == 3:
sel.add(f)
elif len(F[f]) != 2:
uns.add(f)
for f in range(meshkey[2]):
if f in sel:
mesh.polygons[f].select = True
if f in uns:
mesh.polygons[f].select = False
cachedata[meshkey] = vert_to_face_map
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
def register():
bpy.utils.register_module(__name__)
def unregister():
bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
register()

View File

@ -1,161 +0,0 @@
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
bl_info = {
"name": "Vertex Chamfer",
"author": "Andrew Hale (TrumanBlending)",
"version": (0, 1),
"blender": (2, 63, 0),
"location": "Spacebar Menu",
"description": "Chamfer vertex",
"wiki_url": "",
"category": "Mesh"}
import bpy
import bmesh
from bpy.types import Operator
from bpy.props import (
BoolProperty,
FloatProperty,
)
class VertexChamfer(Operator):
bl_idname = "mesh.vertex_chamfer"
bl_label = "Chamfer Vertex"
bl_description = "Tri chamfer selected vertices"
bl_options = {'REGISTER', 'UNDO'}
factor: FloatProperty(
name="Factor",
description="Size of the Champfer",
default=0.1,
min=0.0,
soft_max=1.0
)
relative: BoolProperty(
name="Relative",
description="If Relative, Champfer size is relative to the edge length",
default=True
)
dissolve: BoolProperty(
name="Remove",
description="Remove/keep the original selected vertices\n"
"Remove creates a new triangle face between the Champfer edges,\n"
"similar to the Dissolve Vertices operator",
default=True
)
displace: FloatProperty(
name="Displace",
description="Active only if Remove option is disabled\n"
"Displaces the original selected vertices along the normals\n"
"defined by the Champfer edges",
soft_min=-5.0,
soft_max=5.0
)
@classmethod
def poll(self, context):
return (context.active_object.type == 'MESH' and
context.mode == 'EDIT_MESH')
def draw(self, context):
layout = self.layout
layout.prop(self, "factor", text="Distance" if self.relative else "Factor")
sub = layout.row()
sub.prop(self, "relative")
sub.prop(self, "dissolve")
if not self.dissolve:
layout.prop(self, "displace")
def execute(self, context):
ob = context.active_object
me = ob.data
bm = bmesh.from_edit_mesh(me)
bm.select_flush(True)
fac = self.factor
rel = self.relative
dissolve = self.dissolve
displace = self.displace
for v in bm.verts:
v.tag = False
# Loop over edges to find those with both verts selected
for e in bm.edges[:]:
e.tag = e.select
if not e.select:
continue
elen = e.calc_length()
val = fac if rel else fac / elen
val = min(val, 0.5)
# Loop over the verts of the edge to split
for v in e.verts:
# if val == 0.5 and e.other_vert(v).tag:
# continue
en, vn = bmesh.utils.edge_split(e, v, val)
en.tag = vn.tag = True
val = 1.0 if val == 1.0 else val / (1.0 - val)
# Get all verts which are selected but not created previously
verts = [v for v in bm.verts if v.select and not v.tag]
# Loop over all verts to split their linked edges
for v in verts:
for e in v.link_edges[:]:
if e.tag:
continue
elen = e.calc_length()
val = fac if rel else fac / elen
bmesh.utils.edge_split(e, v, val)
# Loop over all the loops of the vert
for l in v.link_loops:
# Split the face
bmesh.utils.face_split(
l.face,
l.link_loop_next.vert,
l.link_loop_prev.vert
)
# Remove the vert or displace otherwise
if dissolve:
bmesh.utils.vert_dissolve(v)
else:
v.co += displace * v.normal
me.calc_loop_triangles()
return {'FINISHED'}
def register():
bpy.utils.register_class(VertexChamfer)
def unregister():
bpy.utils.unregister_class(VertexChamfer)
if __name__ == "__main__":
register()

View File

@ -1,835 +0,0 @@
# gpl author: PHKG
bl_info = {
"name": "PKHG faces",
"author": "PKHG",
"version": (0, 0, 6),
"blender": (2, 71, 0),
"location": "View3D > Tools > PKHG (tab)",
"description": "Faces selected will become added faces of different style",
"warning": "",
"wiki_url": "",
"category": "Mesh",
}
import bpy
import bmesh
from bpy.types import Operator
from mathutils import Vector
from bpy.props import (
BoolProperty,
StringProperty,
IntProperty,
FloatProperty,
EnumProperty,
)
class MESH_OT_add_faces_to_object(Operator):
bl_idname = "mesh.add_faces_to_object"
bl_label = "Face Extrude"
bl_description = "Set parameters and build object with added faces"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
reverse_faces: BoolProperty(
name="Reverse Faces",
default=False,
description="Revert the normals of selected faces"
)
name_source_object: StringProperty(
name="Mesh",
description="Choose a Source Mesh",
default="Cube"
)
remove_start_faces: BoolProperty(
name="Remove Start Faces",
default=True,
description="Make a choice about removal of Original Faces"
)
base_height: FloatProperty(
name="Base Height",
min=-20,
soft_max=10, max=20,
default=0.2,
description="Set general Base Height"
)
use_relative_base_height: BoolProperty(
name="Relative Base Height",
default=False,
description="Relative or absolute Base Height"
)
second_height: FloatProperty(
name="2nd height", min=-5,
soft_max=5, max=20,
default=0.2,
description="Second height for various shapes"
)
width: FloatProperty(
name="Width Faces",
min=-20, max=20,
default=0.5,
description="Set general width"
)
repeat_extrude: IntProperty(
name="Repeat",
min=1,
soft_max=5, max=20,
description="For longer base"
)
move_inside: FloatProperty(
name="Move Inside",
min=0.0,
max=1.0,
default=0.5,
description="How much move to inside"
)
thickness: FloatProperty(
name="Thickness",
soft_min=0.01, min=0,
soft_max=5.0, max=20.0,
default=0
)
depth: FloatProperty(
name="Depth",
min=-5,
soft_max=5.0, max=20.0,
default=0
)
collapse_edges: BoolProperty(
name="Make Point",
default=False,
description="Collapse the vertices of edges"
)
spike_base_width: FloatProperty(
name="Spike Base Width",
default=0.4,
min=-4.0,
soft_max=1, max=20,
description="Base width of a spike"
)
base_height_inset: FloatProperty(
name="Base Height Inset",
default=0.0,
min=-5, max=5,
description="To elevate or drop the Base height Inset"
)
top_spike: FloatProperty(
name="Top Spike",
default=1.0,
min=-10.0, max=10.0,
description="The Base Height of a spike"
)
top_extra_height: FloatProperty(
name="Top Extra Height",
default=0.0,
min=-10.0, max=10.0,
description="Add extra height"
)
step_with_real_spike: BoolProperty(
name="Step with Real Spike",
default=False,
description="In stepped, use a real spike"
)
use_relative: BoolProperty(
name="Use Relative",
default=False,
description="Change size using area, min or max"
)
face_types: EnumProperty(
name="Face Types",
description="Different types of Faces",
default="no",
items=[
('no', "Pick an Option", "Choose one of the available options"),
('open_inset', "Open Inset", "Inset without closing faces (holes)"),
('with_base', "With Base", "Base and ..."),
('clsd_vertical', "Closed Vertical", "Closed Vertical"),
('open_vertical', "Open Vertical", "Open Vertical"),
('spiked', "Spiked", "Spike"),
('stepped', "Stepped", "Stepped"),
('boxed', "Boxed", "Boxed"),
('bar', "Bar", "Bar"),
]
)
strange_boxed_effect: BoolProperty(
name="Strange Effect",
default=False,
description="Do not show one extrusion"
)
use_boundary: BoolProperty(
name="Use Boundary",
default=True
)
use_even_offset: BoolProperty(
name="Even Offset",
default=True
)
use_relative_offset: BoolProperty(
name="Relative Offset",
default=True
)
use_edge_rail: BoolProperty(
name="Edge Rail",
default=False
)
use_outset: BoolProperty(
name="Outset",
default=False
)
use_select_inset: BoolProperty(
name="Inset",
default=False
)
use_interpolate: BoolProperty(
name="Interpolate",
default=True
)
@classmethod
def poll(cls, context):
result = False
active_object = context.active_object
if active_object:
mesh_objects_name = [el.name for el in bpy.data.objects if el.type == "MESH"]
if active_object.name in mesh_objects_name:
result = True
return result
def draw(self, context):
layout = self.layout
col = layout.column()
col.separator()
col.label(text="Using Active Object", icon="INFO")
col.separator()
col.label(text="Face Types:")
col.prop(self, "face_types", text="")
col.separator()
col.prop(self, "use_relative")
if self.face_types == "open_inset":
col.prop(self, "move_inside")
col.prop(self, "base_height")
elif self.face_types == "with_base":
col.prop(self, "move_inside")
col.prop(self, "base_height")
col.prop(self, "second_height")
col.prop(self, "width")
elif self.face_types == "clsd_vertical":
col.prop(self, "base_height")
elif self.face_types == "open_vertical":
col.prop(self, "base_height")
elif self.face_types == "boxed":
col.prop(self, "move_inside")
col.prop(self, "base_height")
col.prop(self, "top_spike")
col.prop(self, "strange_boxed_effect")
elif self.face_types == "spiked":
col.prop(self, "spike_base_width")
col.prop(self, "base_height_inset")
col.prop(self, "top_spike")
elif self.face_types == "bar":
col.prop(self, "spike_base_width")
col.prop(self, "top_spike")
col.prop(self, "top_extra_height")
elif self.face_types == "stepped":
col.prop(self, "spike_base_width")
col.prop(self, "base_height_inset")
col.prop(self, "top_extra_height")
col.prop(self, "second_height")
col.prop(self, "step_with_real_spike")
def execute(self, context):
obj_name = self.name_source_object
face_type = self.face_types
is_selected = check_is_selected()
if not is_selected:
self.report({'WARNING'},
"Operation Cancelled. No selected Faces found on the Active Object")
return {'CANCELLED'}
if face_type == "spiked":
Spiked(spike_base_width=self.spike_base_width,
base_height_inset=self.base_height_inset,
top_spike=self.top_spike, top_relative=self.use_relative)
elif face_type == "boxed":
startinfo = prepare(self, context, self.remove_start_faces)
bm = startinfo['bm']
top = self.top_spike
obj = startinfo['obj']
obj_matrix_local = obj.matrix_local
distance = None
base_heights = None
t = self.move_inside
areas = startinfo['areas']
base_height = self.base_height
if self.use_relative:
distance = [min(t * area, 1.0) for i, area in enumerate(areas)]
base_heights = [base_height * area for i, area in enumerate(areas)]
else:
distance = [t] * len(areas)
base_heights = [base_height] * len(areas)
rings = startinfo['rings']
centers = startinfo['centers']
normals = startinfo['normals']
for i in range(len(rings)):
make_one_inset(self, context, bm=bm, ringvectors=rings[i],
center=centers[i], normal=normals[i],
t=distance[i], base_height=base_heights[i])
bpy.ops.mesh.select_mode(type="EDGE")
bpy.ops.mesh.select_more()
bpy.ops.mesh.select_more()
bpy.ops.object.mode_set(mode='OBJECT')
# PKHG>INFO base extrusion done and set to the mesh
# PKHG>INFO if the extrusion is NOT done ... it'll look strange soon!
if not self.strange_boxed_effect:
bpy.ops.object.mode_set(mode='EDIT')
obj = context.active_object
bm = bmesh.from_edit_mesh(obj.data)
bmfaces = [face for face in bm.faces if face.select]
res = extrude_faces(self, context, bm=bm, face_l=bmfaces)
ring_edges = [face.edges[:] for face in res]
bpy.ops.object.mode_set(mode='OBJECT')
# PKHG>INFO now the extruded facec have to move in normal direction
bpy.ops.object.mode_set(mode='EDIT')
obj = bpy.context.view_layer.objects.active
bm = bmesh.from_edit_mesh(obj.data)
todo_faces = [face for face in bm.faces if face.select]
for face in todo_faces:
bmesh.ops.translate(bm, vec=face.normal * top, space=obj_matrix_local,
verts=face.verts)
bpy.ops.object.mode_set(mode='OBJECT')
elif face_type == "stepped":
Stepped(spike_base_width=self.spike_base_width,
base_height_inset=self.base_height_inset,
top_spike=self.second_height,
top_extra_height=self.top_extra_height,
use_relative_offset=self.use_relative, with_spike=self.step_with_real_spike)
elif face_type == "open_inset":
startinfo = prepare(self, context, self.remove_start_faces)
bm = startinfo['bm']
# PKHG>INFO adjust for relative, via areas
t = self.move_inside
areas = startinfo['areas']
base_height = self.base_height
base_heights = None
distance = None
if self.use_relative:
distance = [min(t * area, 1.0) for i, area in enumerate(areas)]
base_heights = [base_height * area for i, area in enumerate(areas)]
else:
distance = [t] * len(areas)
base_heights = [base_height] * len(areas)
rings = startinfo['rings']
centers = startinfo['centers']
normals = startinfo['normals']
for i in range(len(rings)):
make_one_inset(self, context, bm=bm, ringvectors=rings[i],
center=centers[i], normal=normals[i],
t=distance[i], base_height=base_heights[i])
bpy.ops.object.mode_set(mode='OBJECT')
elif face_type == "with_base":
startinfo = prepare(self, context, self.remove_start_faces)
bm = startinfo['bm']
obj = startinfo['obj']
object_matrix = obj.matrix_local
# PKHG>INFO for relative (using areas)
t = self.move_inside
areas = startinfo['areas']
base_height = self.base_height
distance = None
base_heights = None
if self.use_relative:
distance = [min(t * area, 1.0) for i, area in enumerate(areas)]
base_heights = [base_height * area for i, area in enumerate(areas)]
else:
distance = [t] * len(areas)
base_heights = [base_height] * len(areas)
next_rings = []
rings = startinfo['rings']
centers = startinfo['centers']
normals = startinfo['normals']
for i in range(len(rings)):
next_rings.append(make_one_inset(self, context, bm=bm, ringvectors=rings[i],
center=centers[i], normal=normals[i],
t=distance[i], base_height=base_heights[i]))
prepare_ring = extrude_edges(self, context, bm=bm, edge_l_l=next_rings)
second_height = self.second_height
width = self.width
vectors = [[ele.verts[:] for ele in edge] for edge in prepare_ring]
n_ring_vecs = []
for rings in vectors:
v = []
for edgv in rings:
v.extend(edgv)
# PKHF>INFO no double verts allowed, coming from two adjacents edges!
bm.verts.ensure_lookup_table()
vv = list(set([ele.index for ele in v]))
vvv = [bm.verts[i].co for i in vv]
n_ring_vecs.append(vvv)
for i, ring in enumerate(n_ring_vecs):
make_one_inset(self, context, bm=bm, ringvectors=ring,
center=centers[i], normal=normals[i],
t=width, base_height=base_heights[i] + second_height)
bpy.ops.object.mode_set(mode='OBJECT')
else:
if face_type == "clsd_vertical":
obj_name = context.active_object.name
ClosedVertical(name=obj_name, base_height=self.base_height,
use_relative_base_height=self.use_relative)
elif face_type == "open_vertical":
obj_name = context.active_object.name
OpenVertical(name=obj_name, base_height=self.base_height,
use_relative_base_height=self.use_relative)
elif face_type == "bar":
startinfo = prepare(self, context, self.remove_start_faces)
result = []
bm = startinfo['bm']
rings = startinfo['rings']
centers = startinfo['centers']
normals = startinfo['normals']
spike_base_width = self.spike_base_width
for i, ring in enumerate(rings):
result.append(make_one_inset(self, context, bm=bm,
ringvectors=ring, center=centers[i],
normal=normals[i], t=spike_base_width))
next_ring_edges_list = extrude_edges(self, context, bm=bm,
edge_l_l=result)
top_spike = self.top_spike
fac = top_spike
object_matrix = startinfo['obj'].matrix_local
for i in range(len(next_ring_edges_list)):
translate_ONE_ring(
self, context, bm=bm,
object_matrix=object_matrix,
ring_edges=next_ring_edges_list[i],
normal=normals[i], distance=fac
)
next_ring_edges_list_2 = extrude_edges(self, context, bm=bm,
edge_l_l=next_ring_edges_list)
top_extra_height = self.top_extra_height
for i in range(len(next_ring_edges_list_2)):
move_corner_vecs_outside(
self, context, bm=bm,
edge_list=next_ring_edges_list_2[i],
center=centers[i], normal=normals[i],
base_height_erlier=fac + top_extra_height,
distance=fac
)
bpy.ops.mesh.select_mode(type="VERT")
bpy.ops.mesh.select_more()
bpy.ops.object.mode_set(mode='OBJECT')
return {'FINISHED'}
def find_one_ring(sel_vertices):
ring0 = sel_vertices.pop(0)
to_delete = []
for i, edge in enumerate(sel_vertices):
len_nu = len(ring0)
if len(ring0 - edge) < len_nu:
to_delete.append(i)
ring0 = ring0.union(edge)
to_delete.reverse()
for el in to_delete:
sel_vertices.pop(el)
return (ring0, sel_vertices)
class Stepped:
def __init__(self, spike_base_width=0.5, base_height_inset=0.0, top_spike=0.2,
top_relative=False, top_extra_height=0, use_relative_offset=False,
with_spike=False):
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=True, use_relative_offset=False,
use_edge_rail=False, thickness=spike_base_width, depth=0, use_outset=True,
use_select_inset=False, use_individual=True, use_interpolate=True
)
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=True, use_relative_offset=use_relative_offset,
use_edge_rail=False, thickness=top_extra_height, depth=base_height_inset,
use_outset=True, use_select_inset=False, use_individual=True, use_interpolate=True
)
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=True, use_relative_offset=use_relative_offset,
use_edge_rail=False, thickness=spike_base_width, depth=0, use_outset=True,
use_select_inset=False, use_individual=True, use_interpolate=True
)
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=True, use_relative_offset=False,
use_edge_rail=False, thickness=0, depth=top_spike, use_outset=True,
use_select_inset=False, use_individual=True, use_interpolate=True
)
if with_spike:
bpy.ops.mesh.merge(type='COLLAPSE')
bpy.ops.object.mode_set(mode='OBJECT')
class Spiked:
def __init__(self, spike_base_width=0.5, base_height_inset=0.0, top_spike=0.2, top_relative=False):
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=True, use_relative_offset=False,
use_edge_rail=False, thickness=spike_base_width, depth=base_height_inset,
use_outset=True, use_select_inset=False, use_individual=True, use_interpolate=True
)
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=True, use_relative_offset=top_relative,
use_edge_rail=False, thickness=0, depth=top_spike, use_outset=True,
use_select_inset=False, use_individual=True, use_interpolate=True
)
bm = bmesh.from_edit_mesh(obj.data)
bpy.ops.mesh.merge(type='COLLAPSE')
bpy.ops.object.mode_set(mode='OBJECT')
class ClosedVertical:
def __init__(self, name="Plane", base_height=1, use_relative_base_height=False):
obj = bpy.data.objects[name]
bm = bmesh.new()
bm.from_mesh(obj.data)
# PKHG>INFO deselect chosen faces
sel = [f for f in bm.faces if f.select]
for f in sel:
f.select = False
res = bmesh.ops.extrude_discrete_faces(bm, faces=sel)
# PKHG>INFO select extruded faces
for f in res['faces']:
f.select = True
factor = base_height
for face in res['faces']:
if use_relative_base_height:
area = face.calc_area()
factor = area * base_height
else:
factor = base_height
for el in face.verts:
tmp = el.co + face.normal * factor
el.co = tmp
me = bpy.data.meshes[name]
bm.to_mesh(me)
bm.free()
class OpenVertical:
def __init__(self, name="Plane", base_height=1, use_relative_base_height=False):
obj = bpy.data.objects[name]
bm = bmesh.new()
bm.from_mesh(obj.data)
# PKHG>INFO deselect chosen faces
sel = [f for f in bm.faces if f.select]
for f in sel:
f.select = False
res = bmesh.ops.extrude_discrete_faces(bm, faces=sel)
# PKHG>INFO select extruded faces
for f in res['faces']:
f.select = True
# PKHG>INFO adjust extrusion by a vector
factor = base_height
for face in res['faces']:
if use_relative_base_height:
area = face.calc_area()
factor = area * base_height
else:
factor = base_height
for el in face.verts:
tmp = el.co + face.normal * factor
el.co = tmp
me = bpy.data.meshes[name]
bm.to_mesh(me)
bm.free()
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.editmode_toggle()
class StripFaces:
def __init__(self, use_boundary=True, use_even_offset=True, use_relative_offset=False,
use_edge_rail=True, thickness=0.0, depth=0.0, use_outset=False,
use_select_inset=False, use_individual=True, use_interpolate=True):
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.inset(
use_boundary=use_boundary, use_even_offset=True, use_relative_offset=False,
use_edge_rail=True, thickness=thickness, depth=depth, use_outset=use_outset,
use_select_inset=use_select_inset, use_individual=use_individual,
use_interpolate=use_interpolate
)
bpy.ops.object.mode_set(mode='OBJECT')
# PKHG>IMFO only 3 parameters inc execution context supported!!
if False:
bpy.ops.mesh.inset(
use_boundary, use_even_offset, use_relative_offset, use_edge_rail,
thickness, depth, use_outset, use_select_inset, use_individual,
use_interpolate
)
elif type == 0:
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=True, use_relative_offset=False,
use_edge_rail=True, thickness=thickness, depth=depth, use_outset=False,
use_select_inset=False, use_individual=True, use_interpolate=True
)
elif type == 1:
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=True, use_relative_offset=False,
use_edge_rail=True, thickness=thickness, depth=depth, use_outset=False,
use_select_inset=False, use_individual=True, use_interpolate=False
)
bpy.ops.mesh.delete(type='FACE')
elif type == 2:
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=False, use_relative_offset=True,
use_edge_rail=True, thickness=thickness, depth=depth, use_outset=False,
use_select_inset=False, use_individual=True, use_interpolate=False
)
bpy.ops.mesh.delete(type='FACE')
elif type == 3:
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=False, use_relative_offset=True,
use_edge_rail=True, thickness=depth, depth=thickness, use_outset=False,
use_select_inset=False, use_individual=True, use_interpolate=True
)
bpy.ops.mesh.delete(type='FACE')
elif type == 4:
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=False, use_relative_offset=True,
use_edge_rail=True, thickness=thickness, depth=depth, use_outset=True,
use_select_inset=False, use_individual=True, use_interpolate=True
)
bpy.ops.mesh.inset(
use_boundary=True, use_even_offset=False, use_relative_offset=True,
use_edge_rail=True, thickness=thickness, depth=depth, use_outset=True,
use_select_inset=False, use_individual=True, use_interpolate=True
)
bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')
def check_is_selected():
is_selected = False
for face in bpy.context.active_object.data.polygons:
if face.select:
is_selected = True
break
return is_selected
def prepare(self, context, remove_start_faces=True):
"""
Start for a face selected change of faces
select an object of type mesh, with activated several (all) faces
"""
obj = bpy.context.view_layer.objects.active
bpy.ops.object.mode_set(mode='OBJECT')
selectedpolygons = [el for el in obj.data.polygons if el.select]
# PKHG>INFO copies of the vectors are needed, otherwise Blender crashes!
centers = [face.center for face in selectedpolygons]
centers_copy = [Vector((el[0], el[1], el[2])) for el in centers]
normals = [face.normal for face in selectedpolygons]
normals_copy = [Vector((el[0], el[1], el[2])) for el in normals]
vertindicesofpolgons = [
[vert for vert in face.vertices] for face in selectedpolygons
]
vertVectorsOfSelectedFaces = [
[obj.data.vertices[ind].co for ind in vertIndiceofface] for
vertIndiceofface in vertindicesofpolgons
]
vertVectorsOfSelectedFaces_copy = [
[Vector((el[0], el[1], el[2])) for el in listofvecs] for
listofvecs in vertVectorsOfSelectedFaces
]
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)
selected_bm_faces = [ele for ele in bm.faces if ele.select]
selected_edges_per_face_ind = [
[ele.index for ele in face.edges] for face in selected_bm_faces
]
indices = [el.index for el in selectedpolygons]
selected_faces_areas = [bm.faces[:][i] for i in indices]
tmp_area = [el.calc_area() for el in selected_faces_areas]
# PKHG>INFO, selected faces are removed, only their edges are used!
if remove_start_faces:
bpy.ops.mesh.delete(type='ONLY_FACE')
bpy.ops.object.mode_set(mode='OBJECT')
obj.data.update()
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)
bm.verts.ensure_lookup_table()
bm.faces.ensure_lookup_table()
start_ring_raw = [
[bm.verts[ind].index for ind in vertIndiceofface] for
vertIndiceofface in vertindicesofpolgons
]
start_ring = []
for el in start_ring_raw:
start_ring.append(set(el))
bm.edges.ensure_lookup_table()
bm_selected_edges_l_l = [
[bm.edges[i] for i in bm_ind_list] for
bm_ind_list in selected_edges_per_face_ind
]
result = {
'obj': obj, 'centers': centers_copy, 'normals': normals_copy,
'rings': vertVectorsOfSelectedFaces_copy, 'bm': bm,
'areas': tmp_area, 'startBMRingVerts': start_ring,
'base_edges': bm_selected_edges_l_l
}
return result
def make_one_inset(self, context, bm=None, ringvectors=None, center=None,
normal=None, t=None, base_height=0):
# a face will get 'inserted' faces to create (normally) a hole if t is > 0 and < 1)
tmp = []
for el in ringvectors:
tmp.append((el * (1 - t) + center * t) + normal * base_height)
tmp = [bm.verts.new(v) for v in tmp] # the new corner bmvectors
# PKHG>INFO so to say sentinells, to use ONE for ...
tmp.append(tmp[0])
vectorsFace_i = [bm.verts.new(v) for v in ringvectors]
vectorsFace_i.append(vectorsFace_i[0])
myres = []
for ii in range(len(vectorsFace_i) - 1):
# PKHG>INFO next line: sequence is important! for added edge
bmvecs = [vectorsFace_i[ii], vectorsFace_i[ii + 1], tmp[ii + 1], tmp[ii]]
res = bm.faces.new(bmvecs)
myres.append(res.edges[2])
myres[-1].select = True # PKHG>INFO to be used later selected!
return (myres)
def extrude_faces(self, context, bm=None, face_l=None):
# to make a ring extrusion
res = bmesh.ops.extrude_discrete_faces(bm, faces=face_l)['faces']
for face in res:
face.select = True
return res
def extrude_edges(self, context, bm=None, edge_l_l=None):
# to make a ring extrusion
all_results = []
for edge_l in edge_l_l:
for edge in edge_l:
edge.select = False
res = bmesh.ops.extrude_edge_only(bm, edges=edge_l)
tmp = [ele for ele in res['geom'] if isinstance(ele, bmesh.types.BMEdge)]
for edge in tmp:
edge.select = True
all_results.append(tmp)
return all_results
def translate_ONE_ring(self, context, bm=None, object_matrix=None, ring_edges=None,
normal=(0, 0, 1), distance=0.5):
# translate a ring in given (normal?!) direction with given (global) amount
tmp = []
for edge in ring_edges:
tmp.extend(edge.verts[:])
# PKHG>INFO no double vertices allowed by bmesh!
tmp = set(tmp)
tmp = list(tmp)
bmesh.ops.translate(bm, vec=normal * distance, space=object_matrix, verts=tmp)
# PKHG>INFO relevant edges will stay selected
return ring_edges
def move_corner_vecs_outside(self, context, bm=None, edge_list=None, center=None,
normal=None, base_height_erlier=0.5, distance=0.5):
# move corners (outside meant mostly) dependent on the parameters
tmp = []
for edge in edge_list:
tmp.extend([ele for ele in edge.verts if isinstance(ele, bmesh.types.BMVert)])
# PKHG>INFO to remove vertices, they are all used twice in the ring!
tmp = set(tmp)
tmp = list(tmp)
for i in range(len(tmp)):
vec = tmp[i].co
direction = vec + (vec - (normal * base_height_erlier + center)) * distance
tmp[i].co = direction
def register():
bpy.utils.register_module(__name__)
def unregister():
bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
register()

View File

@ -1,140 +0,0 @@
# gpl authors: Oscurart, Greg
bl_info = {
"name": "Random Vertices",
"author": "Oscurart, Greg",
"version": (1, 3),
"blender": (2, 63, 0),
"location": "Object > Transform > Random Vertices",
"description": "Randomize selected components of active object",
"warning": "",
"wiki_url": "",
"category": "Mesh"}
import bpy
from bpy.types import Operator
import random
import bmesh
from bpy.props import (
BoolProperty,
FloatProperty,
IntVectorProperty,
)
def add_object(self, context, valmin, valmax, factor, vgfilter):
# select an option with weight map or not
mode = bpy.context.active_object.mode
# generate variables
objact = bpy.context.active_object
listver = []
warn_message = False
# switch to edit mode
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
# bmesh object
odata = bmesh.from_edit_mesh(objact.data)
odata.select_flush(False)
# if the vertex is selected add to the list
for vertice in odata.verts[:]:
if vertice.select:
listver.append(vertice.index)
# If the minimum value is greater than the maximum,
# it adds a value to the maximum
if valmin[0] >= valmax[0]:
valmax[0] = valmin[0] + 1
if valmin[1] >= valmax[1]:
valmax[1] = valmin[1] + 1
if valmin[2] >= valmax[2]:
valmax[2] = valmin[2] + 1
odata.verts.ensure_lookup_table()
random_factor = factor
for vertice in listver:
odata.verts.ensure_lookup_table()
if odata.verts[vertice].select:
if vgfilter is True:
has_group = getattr(objact.data.vertices[vertice], "groups", None)
vertex_group = has_group[0] if has_group else None
vertexweight = getattr(vertex_group, "weight", None)
if vertexweight:
random_factor = factor * vertexweight
else:
random_factor = factor
warn_message = True
odata.verts[vertice].co = (
(((random.randrange(valmin[0], valmax[0], 1)) * random_factor) / 1000) +
odata.verts[vertice].co[0],
(((random.randrange(valmin[1], valmax[1], 1)) * random_factor) / 1000) +
odata.verts[vertice].co[1],
(((random.randrange(valmin[2], valmax[2], 1)) * random_factor) / 1000) +
odata.verts[vertice].co[2]
)
if warn_message:
self.report({'WARNING'},
"Some of the Selected Vertices don't have a Group with Vertex Weight assigned")
bpy.ops.object.mode_set(mode=mode)
class MESH_OT_random_vertices(Operator):
bl_idname = "mesh.random_vertices"
bl_label = "Random Vertices"
bl_description = ("Randomize the location of vertices by a specified\n"
"Multiplier Factor and random values in the defined range\n"
"or a multiplication of them and the Vertex Weights")
bl_options = {'REGISTER', 'UNDO'}
vgfilter: BoolProperty(
name="Vertex Group",
description="Use Vertex Weight defined in the Active Group",
default=False
)
factor: FloatProperty(
name="Factor",
description="Base Multiplier of the randomization effect",
default=1
)
valmin: IntVectorProperty(
name="Min XYZ",
description="Define the minimum range of randomization values",
default=(0, 0, 0)
)
valmax: IntVectorProperty(
name="Max XYZ",
description="Define the maximum range of randomization values",
default=(1, 1, 1)
)
@classmethod
def poll(cls, context):
return (context.object and context.object.type == "MESH" and
context.mode == "EDIT_MESH")
def execute(self, context):
add_object(self, context, self.valmin, self.valmax, self.factor, self.vgfilter)
return {'FINISHED'}
# Registration
def register():
bpy.utils.register_class(MESH_OT_random_vertices)
def unregister():
bpy.utils.unregister_class(MESH_OT_random_vertices)
if __name__ == '__main__':
register()

View File

@ -1,203 +0,0 @@
# -*- coding: utf-8 -*-
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Split Solidify",
"author": "zmj100, updated by zeffii to BMesh",
"version": (0, 1, 2),
"blender": (2, 77, 0),
"location": "View3D > Tool Shelf",
"description": "",
"warning": "",
"wiki_url": "",
"category": "Mesh"}
import bpy
import bmesh
from bpy.types import Operator
from bpy.props import (
EnumProperty,
FloatProperty,
BoolProperty,
)
import random
from math import cos
# define the functions
def solidify_split(self, list_0):
loc_random = self.loc_random
random_dist = self.random_dist
distance = self.distance
thickness = self.thickness
normal_extr = self.normal_extr
bm = self.bm
for fi in list_0:
bm.faces.ensure_lookup_table()
f = bm.faces[fi]
list_1 = []
list_2 = []
if loc_random:
d = random_dist * random.randrange(0, 10)
elif not loc_random:
d = distance
# add new vertices
for vi in f.verts:
bm.verts.ensure_lookup_table()
v = bm.verts[vi.index]
if normal_extr == 'opt0':
p1 = (v.co).copy() + ((f.normal).copy() * d) # out
p2 = (v.co).copy() + ((f.normal).copy() * (d - thickness)) # in
elif normal_extr == 'opt1':
ang = ((v.normal).copy()).angle((f.normal).copy())
h = thickness / cos(ang)
p1 = (v.co).copy() + ((f.normal).copy() * d)
p2 = p1 + (-h * (v.normal).copy())
v1 = bm.verts.new(p1)
v2 = bm.verts.new(p2)
v1.select = False
v2.select = False
list_1.append(v1)
list_2.append(v2)
# add new faces, allows faces with more than 4 verts
n = len(list_1)
k = bm.faces.new(list_1)
k.select = False
for i in range(n):
j = (i + 1) % n
vseq = list_1[i], list_2[i], list_2[j], list_1[j]
k = bm.faces.new(vseq)
k.select = False
list_2.reverse()
k = bm.faces.new(list_2)
k.select = False
bpy.ops.mesh.normals_make_consistent(inside=False)
bmesh.update_edit_mesh(self.me, True)
class MESH_OT_split_solidify(Operator):
bl_idname = "mesh.split_solidify"
bl_label = "Split Solidify"
bl_description = "Split and Solidify selected Faces"
bl_options = {"REGISTER", "UNDO"}
distance: FloatProperty(
name="",
description="Distance of the splitted Faces to the original geometry",
default=0.4,
min=-100.0, max=100.0,
step=1,
precision=3
)
thickness: FloatProperty(
name="",
description="Thickness of the splitted Faces",
default=0.04,
min=-100.0, max=100.0,
step=1,
precision=3
)
random_dist: FloatProperty(
name="",
description="Randomization factor of the splitted Faces' location",
default=0.06,
min=-10.0, max=10.0,
step=1,
precision=3
)
loc_random: BoolProperty(
name="Random",
description="Randomize the locations of splitted faces",
default=False
)
del_original: BoolProperty(
name="Delete original faces",
default=True
)
normal_extr: EnumProperty(
items=(('opt0', "Face", "Solidify along Face Normals"),
('opt1', "Vertex", "Solidify along Vertex Normals")),
name="Normal",
default='opt0'
)
def draw(self, context):
layout = self.layout
layout.label(text="Normal:")
layout.prop(self, "normal_extr", expand=True)
layout.prop(self, "loc_random")
if not self.loc_random:
layout.label(text="Distance:")
layout.prop(self, "distance")
elif self.loc_random:
layout.label(text="Random distance:")
layout.prop(self, "random_dist")
layout.label(text="Thickness:")
layout.prop(self, "thickness")
layout.prop(self, "del_original")
def execute(self, context):
obj = bpy.context.active_object
self.me = obj.data
self.bm = bmesh.from_edit_mesh(self.me)
self.me.update()
list_0 = [f.index for f in self.bm.faces if f.select]
if len(list_0) == 0:
self.report({'WARNING'},
"No suitable selection found. Operation cancelled")
return {'CANCELLED'}
elif len(list_0) != 0:
solidify_split(self, list_0)
context.tool_settings.mesh_select_mode = (True, True, True)
if self.del_original:
bpy.ops.mesh.delete(type='FACE')
else:
pass
return {'FINISHED'}
def register():
bpy.utils.register_class(MESH_OT_split_solidify)
def unregister():
bpy.utils.unregister_class(MESH_OT_split_solidify)
if __name__ == "__main__":
register()

View File

@ -1,301 +0,0 @@
# -*- coding: utf-8 -*-
# ##### 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# Note: Property group was moved to __init__
bl_info = {
"name": "Vertex Align",
"author": "",
"version": (0, 1, 7),
"blender": (2, 61, 0),
"location": "View3D > Tool Shelf",
"description": "",
"warning": "",
"wiki_url": "",
"category": "Mesh"}
import bpy
from bpy.props import (
BoolVectorProperty,
FloatVectorProperty,
)
from mathutils import Vector
from bpy.types import Operator
# Edit Mode Toggle
def edit_mode_out():
bpy.ops.object.mode_set(mode='OBJECT')
def edit_mode_in():
bpy.ops.object.mode_set(mode='EDIT')
def get_mesh_data_():
edit_mode_out()
ob_act = bpy.context.active_object
me = ob_act.data
edit_mode_in()
return me
def list_clear_(l):
l[:] = []
return l
class va_buf():
list_v = []
list_0 = []
# Store The Vertex coordinates
class Vertex_align_store(Operator):
bl_idname = "vertex_align.store_id"
bl_label = "Active Vertex"
bl_description = ("Store Selected Vertex coordinates as an align point\n"
"Single Selected Vertex only")
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')
def execute(self, context):
try:
me = get_mesh_data_()
list_0 = [v.index for v in me.vertices if v.select]
if len(list_0) == 1:
list_clear_(va_buf.list_v)
for v in me.vertices:
if v.select:
va_buf.list_v.append(v.index)
bpy.ops.mesh.select_all(action='DESELECT')
else:
self.report({'WARNING'}, "Please select just One Vertex")
return {'CANCELLED'}
except:
self.report({'WARNING'}, "Storing selection could not be completed")
return {'CANCELLED'}
self.report({'INFO'}, "Selected Vertex coordinates are stored")
return {'FINISHED'}
# Align to original
class Vertex_align_original(Operator):
bl_idname = "vertex_align.align_original"
bl_label = "Align to original"
bl_description = "Align selection to stored single vertex coordinates"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')
def draw(self, context):
layout = self.layout
layout.label(text="Axis:")
row = layout.row(align=True)
row.prop(context.scene.mesh_extra_tools, "vert_align_axis",
text="X", index=0, toggle=True)
row.prop(context.scene.mesh_extra_tools, "vert_align_axis",
text="Y", index=1, toggle=True)
row.prop(context.scene.mesh_extra_tools, "vert_align_axis",
text="Z", index=2, toggle=True)
def execute(self, context):
edit_mode_out()
ob_act = context.active_object
me = ob_act.data
cen1 = context.scene.mesh_extra_tools.vert_align_axis
list_0 = [v.index for v in me.vertices if v.select]
if len(va_buf.list_v) == 0:
self.report({'INFO'},
"Original vertex not stored in memory. Operation Cancelled")
edit_mode_in()
return {'CANCELLED'}
elif len(va_buf.list_v) != 0:
if len(list_0) == 0:
self.report({'INFO'}, "No vertices selected. Operation Cancelled")
edit_mode_in()
return {'CANCELLED'}
elif len(list_0) != 0:
vo = (me.vertices[va_buf.list_v[0]].co).copy()
if cen1[0] is True:
for i in list_0:
v = (me.vertices[i].co).copy()
me.vertices[i].co = Vector((vo[0], v[1], v[2]))
if cen1[1] is True:
for i in list_0:
v = (me.vertices[i].co).copy()
me.vertices[i].co = Vector((v[0], vo[1], v[2]))
if cen1[2] is True:
for i in list_0:
v = (me.vertices[i].co).copy()
me.vertices[i].co = Vector((v[0], v[1], vo[2]))
edit_mode_in()
return {'FINISHED'}
# Align to custom coordinates
class Vertex_align_coord_list(Operator):
bl_idname = "vertex_align.coord_list_id"
bl_label = ""
bl_description = "Align to custom coordinates"
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')
def execute(self, context):
edit_mode_out()
ob_act = context.active_object
me = ob_act.data
list_clear_(va_buf.list_0)
va_buf.list_0 = [v.index for v in me.vertices if v.select][:]
if len(va_buf.list_0) == 0:
self.report({'INFO'}, "No vertices selected. Operation Cancelled")
edit_mode_in()
return {'CANCELLED'}
elif len(va_buf.list_0) != 0:
bpy.ops.vertex_align.coord_menu_id('INVOKE_DEFAULT')
edit_mode_in()
return {'FINISHED'}
# Align to custom coordinates menu
class Vertex_align_coord_menu(Operator):
bl_idname = "vertex_align.coord_menu_id"
bl_label = "Tweak custom coordinates"
bl_description = "Change the custom coordinates for aligning"
bl_options = {'REGISTER', 'UNDO'}
def_axis_coord: FloatVectorProperty(
name="",
description="Enter the values of coordinates",
default=(0.0, 0.0, 0.0),
min=-100.0, max=100.0,
step=1, size=3,
subtype='XYZ',
precision=3
)
use_axis_coord = BoolVectorProperty(
name="Axis",
description="Choose Custom Coordinates axis",
default=(False,) * 3,
size=3,
)
is_not_undo = False
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'MESH')
def using_store(self, context):
scene = context.scene
return scene.mesh_extra_tools.vert_align_use_stored
def draw(self, context):
layout = self.layout
if self.using_store(context) and self.is_not_undo:
layout.label(text="Using Stored Coordinates", icon="INFO")
row = layout.split(0.25)
row.prop(self, "use_axis_coord", index=0, text="X")
row.prop(self, "def_axis_coord", index=0)
row = layout.split(0.25)
row.prop(self, "use_axis_coord", index=1, text="Y")
row.prop(self, "def_axis_coord", index=1)
row = layout.split(0.25)
row.prop(self, "use_axis_coord", index=2, text="Z")
row.prop(self, "def_axis_coord", index=2)
def invoke(self, context, event):
self.is_not_undo = True
scene = context.scene
if self.using_store(context):
self.def_axis_coord = scene.mesh_extra_tools.vert_align_store_axis
return context.window_manager.invoke_props_dialog(self, width=200)
def execute(self, context):
self.is_not_undo = False
edit_mode_out()
ob_act = context.active_object
me = ob_act.data
for i in va_buf.list_0:
v = (me.vertices[i].co).copy()
tmp = Vector((v[0], v[1], v[2]))
if self.use_axis_coord[0] is True:
tmp[0] = self.def_axis_coord[0]
if self.use_axis_coord[1] is True:
tmp[1] = self.def_axis_coord[1]
if self.use_axis_coord[2] is True:
tmp[2] = self.def_axis_coord[2]
me.vertices[i].co = tmp
edit_mode_in()
return {'FINISHED'}
# Register
classes = (
Vertex_align_store,
Vertex_align_original,
Vertex_align_coord_list,
Vertex_align_coord_menu,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()

View File

@ -1,95 +0,0 @@
# gpl author: Stanislav Blinov
bl_info = {
"name": "V/E/F Context Menu",
"author": "Stanislav Blinov",
"version": (1, 0, 1),
"blender": (2, 78, 0),
"description": "Vert Edge Face Double Right Click Edit Mode",
"category": "Mesh",
}
import bpy
import bpy_extras
from bpy.types import (
Menu,
Operator,
)
class MESH_MT_CombinedMenu(Menu):
bl_idname = "mesh.addon_combined_component_menu"
bl_label = "Components"
@classmethod
def poll(cls, context):
return context.mode == 'EDIT_MESH'
def draw(self, context):
layout = self.layout
mode = context.tool_settings.mesh_select_mode
if mode[0]:
layout.menu("VIEW3D_MT_edit_mesh_vertices")
if mode[1]:
layout.menu("VIEW3D_MT_edit_mesh_edges")
if mode[2]:
layout.menu("VIEW3D_MT_edit_mesh_faces")
class MESH_OT_CallContextMenu(Operator):
bl_idname = "mesh.addon_call_context_menu"
bl_label = "Context Menu"
@classmethod
def poll(cls, context):
return context.mode == 'EDIT_MESH'
def execute(self, context):
mode = context.tool_settings.mesh_select_mode
num = sum(int(m) for m in mode)
if num == 1:
if mode[0]:
return bpy.ops.wm.call_menu(name="VIEW3D_MT_edit_mesh_vertices")
if mode[1]:
return bpy.ops.wm.call_menu(name="VIEW3D_MT_edit_mesh_edges")
if mode[2]:
return bpy.ops.wm.call_menu(name="VIEW3D_MT_edit_mesh_faces")
else:
return bpy.ops.wm.call_menu(name=MESH_MT_CombinedMenu.bl_idname)
classes = (
MESH_MT_CombinedMenu,
MESH_OT_CallContextMenu,
)
KEYMAPS = (
# First, keymap identifiers (last bool is True for modal km).
(("3D View", "VIEW_3D", "WINDOW", False), (
# Then a tuple of keymap items, defined by a dict of kwargs
# for the km new func, and a tuple of tuples (name, val)
# for ops properties, if needing non-default values.
({"idname": MESH_OT_CallContextMenu.bl_idname, "type": 'RIGHTMOUSE', "value": 'DOUBLE_CLICK'},
()),
)),
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy_extras.keyconfig_utils.addon_keymap_register(bpy.context.window_manager, KEYMAPS)
def unregister():
bpy_extras.keyconfig_utils.addon_keymap_unregister(bpy.context.window_manager, KEYMAPS)
for cls in classes:
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()