mesh_extra_tools: move to contrib: T63750
This commit is contained in:
parent
01d80b8f60
commit
9e99e90f08
Notes:
blender-bot
2023-02-14 18:07:15 +01:00
Referenced by commit 682d48ce: mesh_tools: restore to release: T637509e99e90f08
Referenced by commit682d48ce
, mesh_tools: restore to release: T637509e99e90f08
|
@ -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()
|
|
@ -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'}
|
|
@ -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 |
|
@ -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()
|
|
@ -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
|
@ -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()
|
|
@ -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
|
@ -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()
|
|
@ -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()
|
|
@ -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'}
|
|
@ -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()
|
|
@ -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()
|
|
@ -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°': 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=[('0°', "0°", "0°"),
|
||||
('15°', "15°", "15°"),
|
||||
('30°', "30°", "30°"),
|
||||
('45°', "45°", "45°"),
|
||||
('60°', "60°", "60°"),
|
||||
('75°', "75°", "75°"),
|
||||
('90°', "90°", "90°"), ],
|
||||
name="Angle Presets",
|
||||
default='0°',
|
||||
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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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"
|
|
@ -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'}
|
|
@ -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'}
|
|
@ -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'}
|
|
@ -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'}
|
|
@ -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'}
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
Loading…
Reference in New Issue