From 9e99e90f08c985cb764ecb3a7bba6ff534d4d874 Mon Sep 17 00:00:00 2001 From: meta-androcto <meta.androcto1@gmail.com> Date: Fri, 24 May 2019 16:03:30 +1000 Subject: [PATCH] mesh_extra_tools: move to contrib: T63750 --- mesh_extra_tools/__init__.py | 905 -------- mesh_extra_tools/face_inset_fillet.py | 335 --- mesh_extra_tools/icons/icons.py | 34 - mesh_extra_tools/icons/ngon.png | Bin 6747 -> 0 bytes mesh_extra_tools/icons/triangle.png | Bin 4153 -> 0 bytes mesh_extra_tools/mesh_check.py | 370 ---- mesh_extra_tools/mesh_cut_faces.py | 266 --- mesh_extra_tools/mesh_edge_roundifier.py | 1380 ------------ mesh_extra_tools/mesh_edges_floor_plan.py | 384 ---- mesh_extra_tools/mesh_edges_length.py | 341 --- mesh_extra_tools/mesh_edgetools.py | 1878 ----------------- mesh_extra_tools/mesh_extrude_and_reshape.py | 378 ---- mesh_extra_tools/mesh_fastloop.py | 112 - mesh_extra_tools/mesh_filletplus.py | 412 ---- mesh_extra_tools/mesh_help.py | 244 --- mesh_extra_tools/mesh_mextrude_plus.py | 370 ---- mesh_extra_tools/mesh_offset_edges.py | 823 -------- mesh_extra_tools/mesh_pen_tool.py | 568 ----- .../mesh_select_tools/__init__.py | 70 - .../mesh_select_tools/mesh_index_select.py | 168 -- .../mesh_select_tools/mesh_info_select.py | 111 - .../mesh_select_by_direction.py | 208 -- .../mesh_select_by_edge_length.py | 234 -- .../mesh_select_tools/mesh_select_by_pi.py | 196 -- .../mesh_select_tools/mesh_select_by_type.py | 75 - .../mesh_select_connected_faces.py | 134 -- .../mesh_selection_topokit.py | 632 ------ mesh_extra_tools/mesh_vertex_chamfer.py | 161 -- mesh_extra_tools/pkhg_faces.py | 835 -------- mesh_extra_tools/random_vertices.py | 140 -- mesh_extra_tools/split_solidify.py | 203 -- mesh_extra_tools/vertex_align.py | 301 --- mesh_extra_tools/vfe_specials.py | 95 - 33 files changed, 12363 deletions(-) delete mode 100644 mesh_extra_tools/__init__.py delete mode 100644 mesh_extra_tools/face_inset_fillet.py delete mode 100644 mesh_extra_tools/icons/icons.py delete mode 100644 mesh_extra_tools/icons/ngon.png delete mode 100644 mesh_extra_tools/icons/triangle.png delete mode 100644 mesh_extra_tools/mesh_check.py delete mode 100644 mesh_extra_tools/mesh_cut_faces.py delete mode 100644 mesh_extra_tools/mesh_edge_roundifier.py delete mode 100644 mesh_extra_tools/mesh_edges_floor_plan.py delete mode 100644 mesh_extra_tools/mesh_edges_length.py delete mode 100644 mesh_extra_tools/mesh_edgetools.py delete mode 100644 mesh_extra_tools/mesh_extrude_and_reshape.py delete mode 100644 mesh_extra_tools/mesh_fastloop.py delete mode 100644 mesh_extra_tools/mesh_filletplus.py delete mode 100644 mesh_extra_tools/mesh_help.py delete mode 100644 mesh_extra_tools/mesh_mextrude_plus.py delete mode 100644 mesh_extra_tools/mesh_offset_edges.py delete mode 100644 mesh_extra_tools/mesh_pen_tool.py delete mode 100644 mesh_extra_tools/mesh_select_tools/__init__.py delete mode 100644 mesh_extra_tools/mesh_select_tools/mesh_index_select.py delete mode 100644 mesh_extra_tools/mesh_select_tools/mesh_info_select.py delete mode 100644 mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py delete mode 100644 mesh_extra_tools/mesh_select_tools/mesh_select_by_edge_length.py delete mode 100644 mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py delete mode 100644 mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py delete mode 100644 mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py delete mode 100644 mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py delete mode 100644 mesh_extra_tools/mesh_vertex_chamfer.py delete mode 100644 mesh_extra_tools/pkhg_faces.py delete mode 100644 mesh_extra_tools/random_vertices.py delete mode 100644 mesh_extra_tools/split_solidify.py delete mode 100644 mesh_extra_tools/vertex_align.py delete mode 100644 mesh_extra_tools/vfe_specials.py diff --git a/mesh_extra_tools/__init__.py b/mesh_extra_tools/__init__.py deleted file mode 100644 index 669d22f00..000000000 --- a/mesh_extra_tools/__init__.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/face_inset_fillet.py b/mesh_extra_tools/face_inset_fillet.py deleted file mode 100644 index 8af709c18..000000000 --- a/mesh_extra_tools/face_inset_fillet.py +++ /dev/null @@ -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'} diff --git a/mesh_extra_tools/icons/icons.py b/mesh_extra_tools/icons/icons.py deleted file mode 100644 index d98c8c063..000000000 --- a/mesh_extra_tools/icons/icons.py +++ /dev/null @@ -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 diff --git a/mesh_extra_tools/icons/ngon.png b/mesh_extra_tools/icons/ngon.png deleted file mode 100644 index b1a79b981a9fbfec54e4c487a45957869df6e4e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6747 zcmeAS@N?(olHy`uVBq!ia0y~yU}OMc4mJh`hM1xiX$%ZfN}eu`Ar*7p-mPB~=Q{Ji zM}2eNOerUhZqW_1Hh1U<F}VkM9OjqZbz@2tccEaw71px9jSKX;yF5A+yBc1v7Y*Bd zf@@LJp9#84yYK3i`Ev-gu(2gcO;df7@^;##w`RPVvu4kJmiB+f&Zj}|_U`>&JHL3| z%<r{!oQhFa?=SB=Z&Uv4%p6IBV-m?dhUkRIdxp0RJ$&MSY!VWG&A&Zs)xE`%{TCBi z6F7D};AfF3;49!NP${r5nDlRQVS@Gs)(WW#j*MBW<X+oET-MZPU_QfIZ&Sd#LCB(_ zZ~x!Y1l@w<;H&i$`p!N%$aR3B=%7B+KF*ph^SV>tvt)K~?^w6;)!Dzt(mo}zCLGxD z;J?%k)*1SDzG!@CUA8J;Jny>3CME-gOU`@WGsfrqTwKQzlgiJ_-YnX{_@HUNe@(&m zHH-UW_~%>>`I_NAKRxQw`#%>niVrg1aV(!Z|4U@~lJjP)o0$Ukt!Eb3s(IJ?a<lKj z9IgNZ@jpfnw!aYlRQTY{vQ^i$YxljJEZX3-w7u>;^Ks4gO`H}N<+!GuT*mNpm&D(5 z{Dy7aTbm8`ZWMHAjQPOb@LwhG<=KN`4{Uuy-(TZ9a;CSJ;nbE%HCFaVBEquR6Q7;q za+rDUB3}ZR<*}4+hnF$5dgg!I%O<l(_Ox9YQ~q{yj?+oX4WH(C)y<Vp@>vejag58M zvhNaK0-NRWv}Fmb3v4WH|J)8gw{fOs1f#}z!CQ=H6rSHWBeI5J%5#Ia3~3XJbJDoN z7*1_=wr-GoA~SntlW4=IJ*#97@D%l(^*PAp;286voiRP@xM8z#aL+P<Fb1xNP4Qke zzt3y78ws=9%uh09-7vRdruX{4b;3Wio{GFFd|-WKa?4xOhdq&uCuVT(V|vFpO{4wN z>g5gnrlGQ1m<@xqpQ@j_P<SxjL*~=;TR#<#{j2%4c?Qo-4h3VqAI%-lf5oqn>Dpr5 z`JL;{CB7qj4stoT#e6U=&@bpI)6uQu+_7L~mbIUSa2SKpgud(lq#vX`aJZWhWzH7E z`)9+-thW(u^F$d^^?tM-DgGOtas1Lo_Xh7-tL`n6Jz=n!X~7BBda)1Ok3#Pr&d~mF za@i{Rt9&UDk&FQou6@gA*w?f6NDuRU)^%HLneT39x?m{&C-BJDzrkCQPoBAa#Flw| z2D_0@+u!B>%VR#sv(Gzh^*_Xqah`X*`!Z!SrWX!TxBs(02)?yZ<dNQkqT9w7?6etl zix19sk@>{`>Q-HJoRS~sysLaE6CxQiJfm*^kAC1*a{XI5<LAc8;y<<34S^37K6a$# z^Xy>zvG1*9M!PL@hGW$2|GFP+ORWFjJz(9iol%_eJ7eRGjo(bC)xF*K<^De1zxQ@q zcNAN(3;pM-pZ(9T*Fc()!#lj}zs%jOe`AkWeu)kKn<O<?=9u*IA9;-1{J#fZWBsrB z$e8Es#RFjv^xtX~MTRl7ERn9xQ{ek;>K=Ap==<&cIyYBv|I2t(&86<S&D#C(f$yEw zY;V|<7{eG?mYgf(-w<KgQ~03$fN5@NU&xa@m3`Y6L_RSSk{9R_+nJccy1-xWhrdAk zadz#tjlC8&ao3Nw?|d*_c)8`Q&!4V$UY--Lbu7R}o59vA`?o!#-9njDRY}k9-+J1< z>jA&ISYv8z-8+#w&X}}nwyNaS47LRa_0`_Jxcuf%@`3P%&AZ?IVR&hL-p?@O+;Mxy zJB}+X|7uO^Wq5Xg)!wP*L^<=W)7ARI&ksn8Cg0F5-B;bPSoE+2^E%ack`m!Pi#5U+ z(oVMRF_!+O8Mtx{!x<+v{=H>_)^*qT_g-?oIDf(J^aYpO7pZP$GGLDW_Uh~goo+rR zZHCJmdiDQGe(4QtKE!oEB<J<&IYQgq_>Hw0ChvH_uXZkeEC136_cwo-3lq|+CO&_D zpy+A9J2gA^%2><3oEvrxM_B8*3UlK(f9_`t7rg$BebHy;CHALp9<iVOEachaBeu+C zO2I~7lpD5e`=9!Vb<VBq-{L&K7$sgdOsT$j-?%{b!`;OD#s*hd71qA}&t&0kIc1?0 zllEfeOLD4m-gaB6+1j=-?066^p#S7T-NL7vmaQ`7zZ)=_kKr%pFa4m{OCevCy9`%I z?qZrSZFwHszK(yl{;?je<0-RJ`y2P=JI^1E9lN$Xy;jE?%2;qv-r2|OEc3HBN<FnH zC4pV)Vo#@wB)_<FL4-m4&(Z_;7w_MeAD-x|JbCq{GRF9<Rlm}^3`^1;aDx)P`va$^ z0c=^h`*kJXO8zkYaMSU=vB8Dk3^5<P8=hZ$`zQIpRGu5R>Y~-&O70N4Ys)Oobbh&v zR0OjnlSlUNc-3`p&wsV4@hscvDsQwiB$dDL{DE?|!t)0Xh%$)(nRuk~Nzl`PcP#r1 zbh3ZvdRDXLG3tf!r80l~c~<PHg$r*8W5L1oD(l|LFOxVHu>S3K)ycNZw>zuZu4x@p z$Wq_}xzGC1t$k{{AMJ9Lj9vRr`GI!xhV%tL+8gwKBsbn)eEa9|#@P***L>ct{`c-Y zw?DhD&FQZf{84*q?;=rKkOQ|faElzi;S_Uk^VYw7OKq9$pZC5#Yw||T?zv{!d54Lt z2UzPl3UmM0JPnXjEc^HNR3VS5_@BfhN*kOkr~K}yW-HS=ZkT@5m+{iBH^nc?{&jBs z%OCmt`s9D_>|6fLYy8~#oJVAP`hppPAeTxN*zU^ytuG}1rSho68M~MdrXLv>KHF2n z|AF_xvY>A~B7qE-Zv9)&uw3TU`Htd!cTW}0(A@DrTwp$D>?#>A=9rK}^BUi9H*nX9 ze(-;>?(KV(b#MPi8L}CB>;2&FtY+g2<Vm*v5bhhgKCmZo|0Z1qy&pf1Z2hY}#ksP} z;tBt$!WotY2g8qOHh$alC+yL@#=xf*%ceYab`bwlcw}qs*>m=MHPRM`t?DZ;m6x%7 zw7xQ@Ki=_<alxD<p3A55SROR}=lQ_=<@z`Kn;~D>j!N_hi2n(C^jq;(HQTzJRlm%R zO59VL%{1ZKw|cHQ%VkbQJ*vx(z9+4adH#Ux0k5Y4atwBJPtR-AS;nCEBe?VZ+w8J` zfe*Y7w6FclKkv)jx*0!Yv(FtruRPDVU{w-N^~yd_NL=sCUJlALCZT_ul6aOW>-`Y! zNO#V9FS$eR$F^tl8uxKKNbQoJ`Ok9B534uRzMQ}I@?U}857#4G|9<~2?L0pxx1J+) zOZo%DNAnub2LDa^rC4c^|7BxyXS#PD<F+r>Q)d)2dYmp!XM8U3JlI(FfrZ;0k&gl! z(jM`@Y_gxeV_W@;82k7OevkgW{VkGga4P1*bK(2i7w;HvXx@>YP}-F#xS{LDaT(+L z5nHGJxbsT!!Nr|==I3@X?0CS>x{lS(`MvsUGY5zNCDYYAL(MggCmK!o#`A~k!o2DC zIYYkedt54BAYI@beAPPbv-G!UhD-i`v^(a;Z}#$<QIY>&x=d~VOr5*F1-2h+C+o=d zeNR;XIIlJOySdVgjSM>;+-J4nnWG>+dzS8z`M-M06x8DND%@pwGu}x~_{3Bg`g;HJ z-~7wgmgxPs+?X!Wb8bJI#YL<AH|I}LRu?K>R-(>ue#t(YrJCFhRUY51OZ3l8G2p6U zv^ak4+u59@F_!H!tRvq&oZsAf;C;~iy6){hjF+mf`Y+fm8h%Lak+;pZZTVHxzVX~) zPT4uVp07;!{F<7}mSXu*j5{9itDpPn{r=B(X7%Xj=6}5`ON<Pcdu?N=u=x6M@3*_P z-?bA0(-lwN_Pmq+x1mfStze07()_Z6|Fu8XP7Mk_tY&uq@6RLuH<oifXo_e2)9n-x zG*5-^=knS|nLhh3ow{NB;=SaH<L$Fn)v;U>QOGsl6KT5SUN7@~CcA{W_qXUiy}{mL z8GdAEeee#;uRr&mXRzawSugH?-i$fjEVOoR?i<6{%vDwX!B?*{rVABsFgY&#`_Db5 zf{AfDw$Z*Zx9feZ1&rqey_@TC=IEsumI8-ewO{|w_}SV#r-vz=(LQfgl(krL!9nI4 zxgT-{@#eE4rJ4UdVG#dQdEoD@i)SvWJ*xg)e}V7FjM|+K`qk$d2b`MM*x$z>f2A*J zTAuQ{x6#kdBfWN38X1Ore>hnAWdFPU%{#XJUr?rS?2Z2|;}17ItJzG>@3}GGRr$%^ zYnOu${9f~U_N66rYUK^vi%XwyRQ5CEZQEKKylTpB%lGReO`P5@|6QW^{6^2r-Z{lT z76>NHE)y}nu}ZeVu=bQ?`}SS||5GPJ&ux@6mbQHUVVdBld#7f6j-O|^d15hNz8quR zrM@Jo=;EDrCuhVhl{r@`_2Cl7N1Kl_Yp?PhkWk$D?31(d0k=;w%brBd=$W}%rnr)O zL!nCS-Ko*7w^(gf$+j3e$*Ddo@w;XgYHRn=>Vxi+(j5nLi}yY{F(YPa%(<6;8w!)= z_djP`GF{@`rJjsQozcEMk}Bo`#n$ZQzI#H<e}+7Ged00Cm&p6sX+D}ke$PKN=T5nC zz0s3Z;qIO4y>2d(_b}X9CDUw}yL0M{$FtVWuMz#Z&z$)#OGfPUy}cFvk$j5<l8-FC z^Ut8__WSqB-#+fW7s7B!FnT(}`7-g#ano<lu1~wLRAzt7!^QJi^=^Kb*t`7>?+iYZ z9|m{D8_P@oR36KCX?1<RYTvr@_rJM=<M?icd=-1ZvuiHjAJ_KzsXfc|ivQX@=()1| ze$uVi2ZOinVA#=?7sftq$Ae<KxhH1qTPhQO>7aYB(C>Gw!GE93H^^md_~El)<*V5Z z-x<m;y-Qmt_&`~ATfPsU-J!Z)vGb30)_%414ZXi*|KiW-q8`ClXU|@>ucd<XQ0|uR z?;3oW&n(E+KE7Sx_p?sl(0HTDmHBGyZ~og?t}a=BBk7SV$MWx&j+uO8c%QM#>UnTi z&&y8kI>it6d%lLftF!UjqmiCmwLaaiN~+5w)b?J;*UJa^tA749p20px`_3}MofhIZ z&+&g~UbbrfyQQC<{})dzJ+c33%4G)Q6-$b%#W(VQTeF^N!aTiiq6b9gaQ_o{Xx1Az zwdfY7`R=@B>VGyXel+=H&Xns@rm(?lhi~$hwNC$-?(O?M&xJ9f`;T2?KiBs39<!Ho z<Xj&p%-_fD5PWra!`HvtHurD7wDsvR5&fs1A`hKi=U=;CKS)|=$#TZejZ+WhN-a)R zHC(RR`NeW2(*`yP;jHYYwQMsS73ZE#K4N#>d?M3?b9VO?E?!g7j`_gda@%BCux5KW zhxu;DSLb+6+Mc|0xq<aS(L=M|m$!<N^t^>$XHQ~HFfG(xuQHo$#wE|^ALNqag%!gX zX05u%XQOsCZqp@J{k>&bKI%d5yzI}^>3lU6ytH1`%b%H#Z5rqHOrP*+g2fS&GUO-g zF|3|wZ+u`G<Fb%TJFWX3e2}lIk*!eE`=Q<t%sTrH<DR`MvtB;uKDOl;t37`OuSLj) zo9eSy+4*kR8|J4raUPotmyT%O<r&*_UAKL1uDPGJl)>hL(`++dgQc0u=|_K0`ys+` zfb~Dm$0G`ww;PnJY`+|sp|@0ca~!J}m)Y)(D_>1!z0G{fdQL#qicJDH7a#A*y!`S+ zSzSir5B|5J4Q9(2zjbZUbd}zD%WZyEIv2x(rg}LG#|<aX9<csierBWLte6iB^(<nX zX1|?+uc~ux+a5mq`DAbYNU@7&Te*&XN#@)%S3XMWpYv|k17XZ(4747-{NHko%OUvc z><01H*|z>WY?GIGK7Y}@Qh)2Bvw{o<nh(7V)%!8I<+h2N%c-+oJ0f4&eNoF5Z!mkN zXU1!=GfUY#diH<k)2s_tzDjQlZ<)O{v*F~_lDvJBeg$OTl4ET6Bk)W@{LjZjxl)&> zPnmq2!~FKE5AwS-8FG0N-$)fso^4jN>FPuKl3<2;N_mearkrQ=$y-ufWW4eHmQ`Pw zCX~JN<WKc0IOxs4z2~LpDc6&+>lygVHoo54Ey!@g<N(h$rS|_9Vi_xBZfM<E=F9A( zx1_jGd*k^n>n1Zzxc69m=gE{wNA-SylI$~;rwg9k6lciZW%YZ}YC(nre|5KHUS?Rl zwpS)+@p_+?r<pDY@c)>Sa+zVVuN8Z_z;7>gD{Y3CA@MIW4+x3>nRqBy%2-?T`;z#; z_{&c@Z^ZU7IMihrH~BL6%<EUU-)<%S&-pv+gTM!+56yC=61I3ezxVox-KCj+pv;hb zAnIY7fB8Xg`2!hedV<b*%7^SW`#SMli7gYuPhP&y4WWl}r8e8I?%1ima`IXG$(bSz zr3dsLn!P=eaM|?boZGH%r02fc!(bA+SNf;@isFOvN_UnS27B#%)-pSG`Y*TTCT<K0 z_E#(gX0y$Bw?^sNoF(Vl|1St<xW=^jQ0}d)hL>F>>w1;`7u1A>fFgQ#!`DM+^@<P5 ztK3;;m_Ml~XJ*0X*TM5&%(=$F@TA=ak~cT6pL!%_@)Dyz?01<quuX{jyd|?Cb9PBz zui}2!x@%WJ=2$oK9y+_O;Gn)h-en1fpuDvLd1cP8xV22&85HclL^9}z=Utw`dAie5 z%`^80|1Z`AQ-#0JCi^n`L@z1M=HJM_B2bsXH#Fb<-;2dl^(tPPMj1Z|^7FJ$sk8aY zXnISTVM6`ij|oqvXZA$8ozeB2yYH9vR2i-VSq{Jc7|&*t$h>4}+V|;$`~!x!3=A<J z{27*WneAk*sGhFAdd9)0P4n6Ju*qoMcr)2IbiUe+IR7FE#)dzWFE~%Ek37ty-=6cF z{ps@m!bO@4{97(8+pCnbWXnYF+$VqLq#Cj^6dbf~e)IFy?FQvtR`GXv)WrXAe9%&; ze0Am6vQ_?KIqTDR_Aofqd0aU*(KqeKoW7jr?LKY)CxkO@$ZR-y@c`d{#l@No4cz}K zQ}QG4^6c5%9UXS|LE%B>KY|e|H{NjjhR)Zx5f@)1#n|xYLWbZcaq}4uH?rJ3$1lnI zUvWRvg<}^^n6b``+d9$uCeMGx%}f_UzN$TNeVF$4nZjl5mv<@+pH`S{(qcH-eCVz0 z(<Xb*9lpj7g3igP-dR(U_g(im(}iO)Czmm%t-Ez%=9*{!CnPdvtojxCK=ff+u2jM^ zFRS!8&z*CpzTt9EvJJ7i##%q8V0N0r(iqFI9XH>!{hyG}n324qB#t|G*1;=o>r%}B zwAXMS2tML#GwF?G!%W?mF*kkAynK@q$#ChM?MubWoO(YNx862kR(sm<Brl|az4&hL ztW-vi9H|5op0gk1Kj|^_3&+2lnsS+8v;8H@O}syw^N*%PGhC|vb18k9ZrQ>8-aCAa z6@v0S=6N!1uzGH+xk;D7Q+oNbTP6p5miOKXu>V=Ewo|;Jt>VCPP_h2f<M}bZB))X* z*Gv=qzE0g|QP|1-&*Pz)@53NJkNsBr)V@z%xmk~)^NT<hi$(Yj-{5`46W{Yk^8a7h z!}LMp!>)qaWg#<ePA~D><9Funn$&oPS*zrjuW^}I@8bSv@X*Y6VUV7Oyuxm?r+L>l z=`(bGaqFtQVRB&4!<ZyLk5grz&!;Q}<;=W?X1S&Zd?uW0y?bQN**HN^*?0GV@}aYO zrQoco>2s=a>7lcBlZwo$T}2vPsys@;)n@R#&?7MuLXLcpKc&Tx-53ZiDV7u`i2Q87 zwpMc)D1UQD=-+u`B>u<oQ0^?JCGYFNS#zD(!Wm2xJowi>W7clHZSpk2^9-ZsQJ((` z_kgoz^w!J+A13{KcYMPAUH4Wkf@e*=a&Xp6(W`xax^?#52}N5ZTtypPsya#q3Nm-G z-{Wcg5*zRJ=bfsk>7Vv9dF=Cgx8!~feNvv6%b3TKr=7^Xe5SSdpT+~}4cQDY&7Hn8 zxSNFTjoooKOf(#n<NovQm^Q0ka);Os{p-y#XYVY@-gllokKvY;#J0>;zs^j%mFK(i z@IABm%{Edy#CP1E$2w*9;C{>x_mt>^w-y}xow6vGmE7Qd%94>-m5eEIufnexls z)lGSQjjxr|kBWvf#C&MKbocNysfuH)^*-+N(_5Cje<S&*{6_2jC3keZ0$C5R+V@*r zdG%@D{=<#!Z;vi4TJru?;-m8GQy+I+c6-@=d9Bte28Qhl|DRpUk#TN}e`hf3i?dVm zm2FdIEIz8SjA6$EeHOdr5{DuWly=R}PkV0lsC?<uV$JaGfFni;QTzY$F9_mGH80Sf z@Ya>zIge!r(}lRboCoGl-7tU26vemCFTVdHApCmuEyo#eLXXV(yRb;+_td`}3VS#G zjBk*Al<Boe_Q&Nr@w|EuB@Yy>`8@lROxvH+ZjZb)7@lXaC;0SlI)8V~a)wOL=k<)y z&E~4NIQQs2e|@k>o>AwR)qc(EiYMaD^|Zb-CH(&2`*PE{hj+Le?mo))nqJEHJdUC7 z0^fldcByj@?-XaS-gJA4-fNk2dl~jD1Go9D+KcrWqPH>|Ec1_iUhs}Jp}UOX*cVNR zk|j_joAnrcL-+IKT<SYKLoV0La?aW{+zy+ftQnH4rqsTh7QpZ?63R{k<&k?FHY;V@ z4U4~|oC4>XNvpRq8>IDhow&Iso_*dK*8c(<yexafJHx$h&uplaQoJWwxTZ)Zd)BIR z|8y8Mp8Gg7tZw+e_<Pj{xd)q;t;!8mO4j>vx}n}#e|x!5@m7wdK8$M9SnS>2bV#ks zS+y(hSB|mwOY2=hFCTwrkUw4fGJjJN7lT9a)z=qhM&G>`@^vlO{Oqa;`&Qo;wfpsI z%HpFMs~8weLjTr2h?cE;a`(Vf*}8cX3oSqHetl|tP@dS*8SM?fy|%Bv!+0kDrSh@H z?8e!%R;~W6+N&aNbKrV|bjJJkyBY<a!B_X^t^6A%8VyR@-Hp`~_J6OH{_t|ys_Puv zvqg412xqis72`7hYgBk{{@<$qC0(<be5U<-bl+R%U9NCOk@yGR2Y0XiWOx6ici;C< zo~quf8*XQIexG*d=fV$y2U1?LEj%xL{iV3^_D9<~cjW(mKYP_Q4HdOuy&tUyrZ*JN zylwjV@d39-cFpq7>mHxa;q_shafY>CWrL7KZ-vI=MPJ@m9{Il6e0jY+`x?E>o5xNR z9+dahVR`@fY~`kQ^XDgp58PU|YIEK8{o?=mw?+I7^H-=+Wnf@n@O1TaS?83{1OQ2w B<6!^* diff --git a/mesh_extra_tools/icons/triangle.png b/mesh_extra_tools/icons/triangle.png deleted file mode 100644 index 05f3a8db8650f03143a26010e79885a4ed2787b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4153 zcmeAS@N?(olHy`uVBq!ia0y~yU}OMc4mJh`hM1xiX$%Yk44y8IAr*7p-pya3d$scT z$M-TWE-ptpPON2#<}oxoJO7eYpywh}MZ>TDXI7`^Y!UsmNH=5Wol~wZO(LGEVV=Pz z6Sm#xc{%$?{XvmiQJQKibU6)=sk!v9>=fN8C@47b^xS<jK32UeonQRzUi!Sq?6WoY z1qbb)uc<t@uiEzc%$wE!*;G6y;Y6w-OZ6NcXG{CBefv_+z{kM8YF7B=(4`_d48|AC z=CH;v#&FJIpR?uY^nGkLoHh(L5m#;O-D<s#vl%4j9u7U=d*J=Lok#c^qgT!P-M-q( zawDTp!0iIN0_lQ_4&HWbZ&qi?$!FN69%3_4%stFjp#34-vHgd1LVaklMMiD!88!pK zzpZ=o8K<+#ze;@YInH1XoAd#eExgN_|8vf<zN9#xQEy#VoYcz-u1ynnF}(G=U10Me z_t@<}UylDOtIrWV!xpe_z2Tjs`$A)`$Van&7Fm#xdsv?N|BM|y_A9ggX%_NEP3Bz? zkbAiEfPTgAyEQ9i814mM-7{|o-`a!04Mnm3zs~Z<@?KxA=k&UeNh9EPfz}7_2T!hS z{SwDow|P~iWvOCRudu_+bxQNk^2h3iFV%Bey_s>!%TrfabtWYH-fCRV(E3vU-(M*U z#Zukl70eaW#hFh(VhNbnKk5Fn_I1npm+3jJ-pDxR`Q4S=H{8x`&195zsH{^iWxRc2 zr<ra7v&MNzBndyy*aK!o>wLLyFr1qF*}b3`!u(v1q=s*aUV^mc^<}I%3{NY~kPLc! z;NTTkodN+LUcFx41qPvK6(8(g`8wjk?%G_f4&DVm-<RqoG|FUO;>%%3{T2RT=M~qS zPcpH-T0OiA974~|F4_L))KTABFh7RhN$mNZ_7_=!jX4ZYcQIP%l?s-a-kGqAW9@N= z2~1zN|Na;M=_G3fbH)7a!utKf4m0;T+0B;U6}4~DE{?V5I2Rb?9zOo^9rKLE-K%H0 z2a7L!dXs_c(*1wG-4Cz_UEkEYyn(;^_lKeiu7GL!liojTU$;$vnV!?>VkQlj+XY;A z*B<cmjy=uCcj>Oyo2ge?Gt&JSp8gQ}nP2xJ>rc=tr7be*4NO~jpEKF3^<V!xy^!Na zLg;6oS58}O)f<fd=o-$h&;OsEas1-dHD@dtQ`Y7FaDK3`Vd;EEyBq&M_cs<Z_Z3!# zrvJQ{`Zb3^GF!Tjy^d>+?1n$;t7g@|?=RN;kk5F$LhMJby~Y%Mf5uN`wuZC+AKY$c z@9_V;p!^HV+3(T=md#->x@tCu@z0!x`ON>+?&d#T{OR|tw#_e>^*&~|ySMs4_3ByT z>~Z1D1xzQdujSeQlkuHJ*=Oc2|KIjCJf7?GeY1wMAA@9;bRB<1(?jVm-6i>#Ds#IZ z&vW^nws>Lw$5M$O9-*J@nb)t5En+(1bDN<+-LT&L_x4+V82<jhEnjel_rbY;>A`L} z3_pJ|6*wN)<<In8=VR&jFY(q}xL?$nr5*`x=-I-nekt~cb@79x{LJEEUyFAB%{~0{ zz<0@i;zzhk#989yD`MB4<W$iAEm`-O`OfY)&)8q=|8}pz>YU0m5$Eh8rV}m+XRc?L z>@N-9l^=h5LwlZ-W#)sb9{$rE!VY$elJ@1rmVRdba$or4r@0>!B{wprEa?Ba%PQgJ z^TyldAOCLup3U?2)xY$xB(sJizh<nrZk~2O`s$vI@65m7m;RXP_##g1%g^aE>KR|U z9<8yBx>}RIz57eH&92`?_YVHpz;lB^=vDH*JkE$;0tI&)UdOthiS5XHDbK8$#O$H1 z_3ZWQ(6hnLy4Ti!{krhl?IS-n9I25sn0R2u`spv@zFAv5S(JM?vqYbJp4F3G67tig z2s>B>8pfOR@BPX6%QRoEDz=B;`xM&*hua0LA5MZ2#yhTv@4Dq*k4xNA|9#v=`Ljqu zO7hVfS3|p;s5uPB%)QQpi#$HO>?mh~OY(8Bu+<!fW2RXU;lrE>Dl3!rEs~Ho<Mm@u z_K^H6di2MJ%ngh^%B5fxT+cNfHpGBcgqf)~R74&DnXx9vkD;-s`)5~DO~lSQ3`wb? zV0}e58JbdLz)Yr_3`|cTOrxI*j-9Z2rrq^1@##iJo;3?*tk*c!E_#H`V2={SV6k{M znSe~g_`n{1?RMb<8@jr{CR^q(Fl$<XHL`vXNnltCcE3)&AH!h{h<QJYnQkn7Fk}7q zP}B3gd)}?M)pmc;Z=05niIPQ3H};;Gv3`-cy~BTF3HfQ7>J70oqQGAI^ps`8GR<Qk zf9S-UHGK6o1zREcNhE<cxF2kD-5iE%#md5cKf8ov<jax|?3(<XWy7MKV883=`!TGZ z?Fum}i8;bC;mq~oCz78<pO$e{_BVe_)ZWCXqiOLhJHF2_KJeH4()bIXj<v5nz@AmP zxd&{zTn>Y`!87fikBOZ(8KxP(28YMe=cgO2Ir@H9`G<b~bl|nv(p-zF?~0j@wW~g0 z*|7P<jP;TEbq)Uv4eWFTr4N|({FLhbnAlmtwShHx-?7x12q`~?Xya$uF2~xJK4jU@ zU>Kh%A#Y}<-f$F@+RH!ZudUBfx+j!db3<$nLto;V@Tg<$y{9=7CO??5Ui!`->(}y& zrJ1Ig+2t@wAGoNLyl<c6hIb*g!jBKTJ=Anq-~aO$>xT3POVwpRi>eyfy<xb!a>mC* z3&xx><-_@PGb+x6PdV^ogRO*oS&J~kv6`o+er%}lV~|bWXOvPCvFD~aL-M{&=`|4x zB0;QA=`|5+BL9Cla<s<Q==hBF8h?|2dK|d4xu1V_F0;Y2)fUIvS!d)jE0{fd{j#An zeQmu@FTeB+op?5rXRCdVwe#k$6K*h!XEn0Rso0#&aI8l3{ErQcXJ3gNn6W<7z%Iw4 zcs4`N&shwQ54Rc5=2S3v_L_IseJQoSJuCY8e`_Az-&!5^F|l|S!=j{p?+QNmudhGz zo+F|v>|^5Q86bUXj}MpSe*)QXIko1-JKueb31_Zfk(A%YJ=u=c;90g@4}bLiE9wl% z`_c~l*znc2jLG0xc3cmC^!X`Z!6QF5eD&GHDDl}e`dItgj3V9xGuHn!vdbx*{JYWM zS@yLa{^+wyq#6w4UrEYuTjFWT(DPI1So_)}BYkG6&r8qt@JFAUqQ;QCucYN;;^~<# z%p5&GtE7_md6kK`%-Q9uX1C@?FhlabEwYafZ!>S@RM2|%y3V}*=db;Te=|>;=TZ}K z5#;o!wvUO?+O<pu&%CXT>~gY?`Y{}<$rL@-t{e7*CE?8Uu+*9xNv>uL52Sj2UV5eU z`0zT;2=kc2<qUr{z8ClWyd>51`0%~xTVEm`v22h}-nV7hgC86IGQMup$^sebY+&~$ zcd0MP$m>1))zOB$2WIH=p7~+Gl?f8dIM!a;r6H8OPvpRl4NM@fa+$sUvEhw0!?7C8 z3wItL-c%=fVTOKh;<=BBJN+5Tlg><UVm;pO8oNi%WgG8;{+L}R37N_Jws?fp+*oRP zygjv&E8)!apc%<E5#X4Yx-0Ye@Fq|s2YD5D^PesRx#{E?T|1rCpy*zG;(V5N?}Ia) zYaSimG?SqzdEd9)Kew*=w^Qij&3|tm9p045An|$Wo-;~zI^Ce4oU+*RNc&SGMv2c$ z-<(mj)A<Pspdz!cKQ_pK;$h0#mB-sva~Y1+tQ45s%kO;+Y@*cXr3=5DU%J=vw0bYU zw<V*?u^QJs^P`Qg*)LZUm!GzT@e3o*v6_`86F(+8w@=hjy~<Kxe5_`r!NHG-f{iNH zQh!q&lJ|vdT6wg6DJaziZCoZMZ<fVu@XTA-aORH<7yTG}eqNg5qiUCPhV#G-ecxjt zj}E)dWa#;MNyJytE@ur`@SMn_!-qhL&Lnl|@%C0fhT1O|!<+k(&rF}h-N*kstE494 zi{=5-13wvGOMPBC$K`1IT>pZah#Q&>hVio|2R%Nl1lBCQ^jLeV4=CpPyn6V%XMt>a zGett)tODd7<M5E08w$%Aat{|CkSuwweV^f;{gGYAf2{Ge*K)jFAX*TA;F3S{^-Hl5 zH5*rz%lmB#W6qF1$F`0^kNq5@pZd3^N6MjR<yqG0^RCMM!T(^=0cRPuIHo!_8x~MY zeD^CQZ}WzK+yBhJbW~~X?pKXv4+TCXPOb}?wC&;F1N|K*_uV*UTr!jC|Asdu8?NNP zty}Vwv%vguUY)@+Z&t1z{@dqvmQ;UH|2yyQ#cje4w+oCu=swtV_xd)m<b5G)4*b}V zYRglzZ&hXKYW*8UOfSxVp09BC8GG`+kPRTYST>uB@1M4cnKk^&?e)J9`@s9?41Hh6 zkeVBt&MnMl;<lgsxi7e3+rzsD>MzIM^glU6-`6Rj=7v@rQ{DbmKd(OfUvY)yg4rAn z8;4(K^Uq9IQ#P>65nayA|E2m*N3uWTJ2pF$D9>)!d+oknsroAW<Da&_`1V84-H-8B z+vgYOeg{{#yu0gn=}JxW4Rdzg*UF)HpNi+yO+GYTShRSJcSHO#J*Qb4^qIw->$xtN z)o@+9w>;)jhTXmQKb5aD$n0ENxl?SxtM@lPXSeYiPMEU(dP8~cg}KGMe=e=G<lK?M z{G;TRmx}7gm%I<wTnY8?dbEZ&X;QW7)BN*n1!=FeR8$L}vAwigt)sa%*u0^6<?0Dj zjvVAQNROYjlG%(Qb01&MfmeZtCaFEV$@`!hEc@N8!M!nDNL(p1W<5iH-WPL$3X4+H zNp4>YLW^Il?PtEf_w97AS+jO8JkPu$>mT=WhD<%Vci;a6PE0Uon6LCrZT{&U%sVs- zd4pP_8(s@vFPM?Hp85D4@ddd*q&}o4hi^H_ec*k_R&iBR+uXyNCEsuO{QNn4|N5+d zf7aGpe%t;xo~ge#`n=$J=I~uVO+)_{|MuGTyo#&;;@gkPeOU~lXJ;RH{@}&#_MRuV zRy}Ly?9=XDR^PY$#DhPgA9mLD%YW?q{CCwe)d$8s-^CstKCo}qvv@bFc~e6E$J}@S zen<W=;{)Tr_w05}Tf1*@?hWQU>~q8;Ze$;j4=w$4Tg`KEw)8rte=~3RoKtT+ANKW8 z{EO(gkMH+COg>=i*lNoj^Wxd{n6Kyma=-lM)KcBhpS$&E;%v@@&D$O-Jur)?>3%4> zm*<b^hw|g|`aO!TxPSi7TA+NG<%^A9dDPy=tbdnEF*w|2C@?LMEnsxt-<}|Op7}e| z_R6L4Rm=aS9;!Vc9$~xYyTAwL2YwH%L-*b>*S*VNmM!hatjDa!er{oP?B^-I@t@f4 zwziAEc-Q?eA@uVVtK*ZNt;qesP{8_ubJyRk>=ucar|)svzQ2ym#^w9FeN!**y_Ut$ zy@gkuA)R48lfM4#MwPc~<{$89^<#@$^XkcVsp-gl?J3>=+57eumBt9Zz0SbEz~JfX K=d#Wzp$PyJ{@G0c diff --git a/mesh_extra_tools/mesh_check.py b/mesh_extra_tools/mesh_check.py deleted file mode 100644 index 2acfe184b..000000000 --- a/mesh_extra_tools/mesh_check.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_cut_faces.py b/mesh_extra_tools/mesh_cut_faces.py deleted file mode 100644 index 1522b1597..000000000 --- a/mesh_extra_tools/mesh_cut_faces.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_edge_roundifier.py b/mesh_extra_tools/mesh_edge_roundifier.py deleted file mode 100644 index f216b1dbe..000000000 --- a/mesh_extra_tools/mesh_edge_roundifier.py +++ /dev/null @@ -1,1380 +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": "Edge Roundifier", - "category": "Mesh", - "author": "Piotr Komisarczyk (komi3D), PKHG", - "version": (1, 0, 1), - "blender": (2, 73, 0), - "location": "SPACE > Edge Roundifier or CTRL-E > " - "Edge Roundifier or Tools > Addons > Edge Roundifier", - "description": "Mesh editing script allowing edge rounding", - "wiki_url": "", - "category": "Mesh" -} - -import bpy -import bmesh -from bpy.types import Operator -from bpy.props import ( - BoolProperty, - FloatProperty, - EnumProperty, - IntProperty, - ) -from math import ( - sqrt, acos, pi, - radians, degrees, sin, - ) -from mathutils import ( - Vector, Euler, - Quaternion, - ) - -# CONSTANTS -two_pi = 2 * pi -XY = "XY" -XZ = "XZ" -YZ = "YZ" -SPIN_END_THRESHOLD = 0.001 -LINE_TOLERANCE = 0.0001 -d_XABS_YABS = False -d_Edge_Info = False -d_Plane = False -d_Radius_Angle = False -d_Roots = False -d_RefObject = False -d_LineAB = False -d_Selected_edges = False -d_Rotate_Around_Spin_Center = False - -# Enable debug prints -DEBUG = False - - -# for debugging PKHG # -def debugPrintNew(debugs, *text): - if DEBUG and debugs: - tmp = [el for el in text] - for row in tmp: - print(row) - - -# Geometry and math calculation methods # - -class CalculationHelper: - - def __init__(self): - """ - Constructor - """ - def getLineCoefficientsPerpendicularToVectorInPoint(self, point, vector, plane): - x, y, z = point - xVector, yVector, zVector = vector - destinationPoint = (x + yVector, y - xVector, z) - if plane == 'YZ': - destinationPoint = (x, y + zVector, z - yVector) - if plane == 'XZ': - destinationPoint = (x + zVector, y, z - xVector) - return self.getCoefficientsForLineThrough2Points(point, destinationPoint, plane) - - def getQuadraticRoots(self, coef): - if len(coef) != 3: - return None # Replaced NaN with None - else: - a, b, c = coef - delta = b ** 2 - 4 * a * c - if delta == 0: - x = -b / (2 * a) - return (x, x) - elif delta < 0: - return None - else: - x1 = (-b - sqrt(delta)) / (2 * a) - x2 = (-b + sqrt(delta)) / (2 * a) - return (x1, x2) - - def getCoefficientsForLineThrough2Points(self, point1, point2, plane): - x1, y1, z1 = point1 - x2, y2, z2 = point2 - - # mapping x1,x2, y1,y2 to proper values based on plane - if plane == YZ: - x1 = y1 - x2 = y2 - y1 = z1 - y2 = z2 - if plane == XZ: - y1 = z1 - y2 = z2 - - # Further calculations the same as for XY plane - xabs = abs(x2 - x1) - yabs = abs(y2 - y1) - debugPrintNew(d_XABS_YABS, "XABS = " + str(xabs) + " YABS = " + str(yabs)) - - if xabs <= LINE_TOLERANCE: - return None # this means line x = edgeCenterX - if yabs <= LINE_TOLERANCE: - A = 0 - B = y1 - return A, B - A = (y2 - y1) / (x2 - x1) - B = y1 - (A * x1) - return (A, B) - - def getLineCircleIntersections(self, lineAB, circleMidPoint, radius): - # (x - a)**2 + (y - b)**2 = r**2 - circle equation - # y = A*x + B - line equation - # f * x**2 + g * x + h = 0 - quadratic equation - A, B = lineAB - a, b = circleMidPoint - f = 1 + (A ** 2) - g = -2 * a + 2 * A * B - 2 * A * b - h = (B ** 2) - 2 * b * B - (radius ** 2) + (a ** 2) + (b ** 2) - coef = [f, g, h] - roots = self.getQuadraticRoots(coef) - if roots is not None: - x1 = roots[0] - x2 = roots[1] - point1 = [x1, A * x1 + B] - point2 = [x2, A * x2 + B] - return [point1, point2] - else: - return None - - def getLineCircleIntersectionsWhenXPerpendicular(self, edgeCenter, - circleMidPoint, radius, plane): - # (x - a)**2 + (y - b)**2 = r**2 - circle equation - # x = xValue - line equation - # f * x**2 + g * x + h = 0 - quadratic equation - xValue = edgeCenter[0] - if plane == YZ: - xValue = edgeCenter[1] - if plane == XZ: - xValue = edgeCenter[0] - - a, b = circleMidPoint - f = 1 - g = -2 * b - h = (a ** 2) + (b ** 2) + (xValue ** 2) - 2 * a * xValue - (radius ** 2) - coef = [f, g, h] - roots = self.getQuadraticRoots(coef) - if roots is not None: - y1 = roots[0] - y2 = roots[1] - point1 = [xValue, y1] - point2 = [xValue, y2] - return [point1, point2] - else: - return None - - # point1 is the point near 90 deg angle - def getAngle(self, point1, point2, point3): - distance1 = (Vector(point1) - Vector(point2)).length - distance2 = (Vector(point2) - Vector(point3)).length - cos = distance1 / distance2 - - if abs(cos) > 1: # prevents Domain Error - cos = round(cos) - - alpha = acos(cos) - return (alpha, degrees(alpha)) - - # get two of three coordinates used for further calculation of spin center - # PKHG>nice if rescriction to these 3 types or planes is to be done - # komi3D> from 0.0.2 there is a restriction. In future I would like Edge - # komi3D> Roundifier to work on Normal and View coordinate systems - def getCircleMidPointOnPlane(self, V1, plane): - X = V1[0] - Y = V1[1] - if plane == 'XZ': - X = V1[0] - Y = V1[2] - elif plane == 'YZ': - X = V1[1] - Y = V1[2] - return [X, Y] - - def getEdgeReference(self, edge, edgeCenter, plane): - vert1 = edge.verts[1].co - V = vert1 - edgeCenter - orthoVector = Vector((V[1], -V[0], V[2])) - if plane == 'XZ': - orthoVector = Vector((V[2], V[1], -V[0])) - elif plane == 'YZ': - orthoVector = Vector((V[0], V[2], -V[1])) - refPoint = edgeCenter + orthoVector - return refPoint - - -# Selection Methods # - -class SelectionHelper: - - def selectVertexInMesh(self, mesh, vertex): - bpy.ops.object.mode_set(mode="OBJECT") - for v in mesh.vertices: - if v.co == vertex: - v.select = True - break - - bpy.ops.object.mode_set(mode="EDIT") - - def getSelectedVertex(self, mesh): - bpy.ops.object.mode_set(mode="OBJECT") - for v in mesh.vertices: - if v.select is True: - bpy.ops.object.mode_set(mode="EDIT") - return v - - bpy.ops.object.mode_set(mode="EDIT") - return None - - def refreshMesh(self, bm, mesh): - bpy.ops.object.mode_set(mode='OBJECT') - bm.to_mesh(mesh) - bpy.ops.object.mode_set(mode='EDIT') - - -# Operator - -class EdgeRoundifier(Operator): - bl_idname = "mesh.edge_roundifier" - bl_label = "Edge Roundifier" - bl_description = "Mesh modeling tool for building arcs on selected Edges" - bl_options = {'REGISTER', 'UNDO', 'PRESET'} - - threshold = 0.0005 - obj = None - - edgeScaleFactor: FloatProperty( - name="", - description="Set the Factor of scaling", - default=1.0, - min=0.00001, max=100000.0, - step=0.5, - precision=5 - ) - r: FloatProperty( - name="", - description="User Defined arc steepness by a Radius\n" - "Enabled only if Entry mode is set to Radius\n", - default=1, - min=0.00001, max=1000.0, - step=0.1, - precision=3 - ) - a: FloatProperty( - name="", - description="User defined arc steepness calculated from an Angle\n" - "Enabled only if Entry mode is set to Angle and\n" - "Angle presets is set Other", - default=180.0, - min=0.1, max=180.0, - step=0.5, - precision=1 - ) - n: IntProperty( - name="", - description="Arc subdivision level", - default=4, - min=1, max=100, - step=1 - ) - flip: BoolProperty( - name="Flip", - description="If True, flip the side of the selected edges where the arcs are drawn", - default=False - ) - invertAngle: BoolProperty( - name="Invert", - description="If True, uses an inverted angle to draw the arc (360 degrees - angle)", - default=False - ) - fullCircles: BoolProperty( - name="Circles", - description="If True, uses an angle of 360 degrees to draw the arcs", - default=False - ) - bothSides: BoolProperty( - name="Both sides", - description="If True, draw arcs on both sides of the selected edges", - default=False - ) - drawArcCenters: BoolProperty( - name="Centers", - description="If True, draws a vertex for each spin center", - default=False - ) - removeEdges: BoolProperty( - name="Edges", - description="If True removes the Original selected edges", - default=False - ) - removeScaledEdges: BoolProperty( - name="Scaled edges", - description="If True removes the Scaled edges (not part of the arcs)", - default=False - ) - connectArcWithEdge: BoolProperty( - name="Arc - Edge", - description="Connect Arcs to Edges", - default=False - ) - connectArcs: BoolProperty( - name="Arcs", - description="Connect subsequent Arcs", - default=False - ) - connectScaledAndBase: BoolProperty( - name="Scaled - Base Edge", - description="Connect Scaled to Base Edge", - default=False - ) - connectArcsFlip: BoolProperty( - name="Flip Arcs", - description="Flip the connection of subsequent Arcs", - default=False - ) - connectArcWithEdgeFlip: BoolProperty( - name="Flip Arc - Edge", - description="Flip the connection of the Arcs to Edges", - default=False - ) - axisAngle: FloatProperty( - name="", - description="Rotate Arc around the perpendicular axis", - default=0.0, - min=-180.0, max=180.0, - step=0.5, - precision=1 - ) - edgeAngle: FloatProperty( - name="", - description="Rotate Arc around the Edge (Edge acts like as the axis)", - default=0.0, - min=-180.0, max=180.0, - step=0.5, - precision=1 - ) - offset: FloatProperty( - name="", - description="Offset Arc perpendicular the Edge", - default=0.0, - min=-1000000.0, max=1000000.0, - step=0.1, - precision=5 - ) - offset2: FloatProperty( - name="", - description="Offset Arc in parallel to the Edge", - default=0.0, - min=-1000000.0, max=1000000.0, - step=0.1, - precision=5 - ) - ellipticFactor: FloatProperty( - name="", - description="Make Arc elliptic", - default=0.0, - min=-1000000.0, max=1000000.0, - step=0.1, - precision=5 - ) - workModeItems = [("Normal", "Normal", ""), ("Reset", "Reset", "")] - workMode: EnumProperty( - items=workModeItems, - name="", - default='Normal', - description="Normal work with the current given parameters set by the user\n" - "Reset - changes back the parameters to their default values" - ) - entryModeItems = [("Radius", "Radius", ""), ("Angle", "Angle", "")] - entryMode: EnumProperty( - items=entryModeItems, - name="", - default='Angle', - description="Entry mode switch between Angle and Radius\n" - "If Angle is selected, arc radius is calculated from it" - ) - rotateCenterItems = [ - ("Spin", "Spin", ""), ("V1", "V1", ""), - ("Edge", "Edge", ""), ("V2", "V2", "") - ] - rotateCenter: EnumProperty( - items=rotateCenterItems, - name="", - default='Edge', - description="Rotate center for spin axis rotate" - ) - arcModeItems = [("FullEdgeArc", "Full", "Full"), ('HalfEdgeArc', "Half", "Half")] - arcMode: EnumProperty( - items=arcModeItems, - name="", - default='FullEdgeArc', - description="Arc mode - switch between Full and Half arcs" - ) - angleItems = [ - ('Other', "Other", "User defined angle"), ('180', "180", "HemiCircle (2 sides)"), - ('120', "120", "TriangleCircle (3 sides)"), ('90', "90", "QuadCircle (4 sides)"), - ('72', "72", "PentagonCircle (5 sides)"), ('60', "60", "HexagonCircle (6 sides)"), - ('45', "45", "OctagonCircle (8 sides)"), ('30', "30", "DodecagonCircle (12 sides)") - ] - angleEnum: EnumProperty( - items=angleItems, - name="", - default='180', - description="Presets prepare standard angles and calculate proper ray" - ) - refItems = [('ORG', "Origin", "Use Origin Location"), ('CUR', "3D Cursor", "Use 3DCursor Location"), - ('EDG', "Edge", "Use Individual Edge Reference")] - referenceLocation: EnumProperty( - items=refItems, - name="", - default='ORG', - description="Reference location used to calculate initial centers of drawn arcs" - ) - planeItems = [ - (XY, "XY", "XY Plane (Z=0)"), - (YZ, "YZ", "YZ Plane (X=0)"), - (XZ, "XZ", "XZ Plane (Y=0)") - ] - planeEnum: EnumProperty( - items=planeItems, - name="", - default='XY', - description="Plane used to calculate spin plane of drawn arcs" - ) - edgeScaleCenterItems = [ - ('V1', "V1", "v1 - First Edge's Vertex"), - ('CENTER', "Center", "Center of the Edge"), - ('V2', "V2", "v2 - Second Edge's Vertex") - ] - edgeScaleCenterEnum: EnumProperty( - items=edgeScaleCenterItems, - name="Edge scale center", - default='CENTER', - description="Center used for scaling the initial edge" - ) - - calc = CalculationHelper() - sel = SelectionHelper() - - @classmethod - def poll(cls, context): - obj = context.active_object - return (obj and obj.type == 'MESH' and - obj.mode == 'EDIT') - - def prepareMesh(self, context): - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') - - mesh = context.view_layer.objects.active.data - bm = bmesh.new() - bm.from_mesh(mesh) - - edges = [ele for ele in bm.edges if ele.select] - return edges, mesh, bm - - def prepareParameters(self): - parameters = {"a": "a"} - parameters["arcMode"] = self.arcMode - parameters["edgeScaleFactor"] = self.edgeScaleFactor - parameters["edgeScaleCenterEnum"] = self.edgeScaleCenterEnum - parameters["plane"] = self.planeEnum - parameters["radius"] = self.r - parameters["angle"] = self.a - parameters["segments"] = self.n - parameters["fullCircles"] = self.fullCircles - parameters["invertAngle"] = self.invertAngle - parameters["bothSides"] = self.bothSides - parameters["angleEnum"] = self.angleEnum - parameters["entryMode"] = self.entryMode - parameters["workMode"] = self.workMode - parameters["refObject"] = self.referenceLocation - parameters["flip"] = self.flip - parameters["drawArcCenters"] = self.drawArcCenters - parameters["removeEdges"] = self.removeEdges - parameters["removeScaledEdges"] = self.removeScaledEdges - parameters["connectArcWithEdge"] = self.connectArcWithEdge - parameters["connectScaledAndBase"] = self.connectScaledAndBase - parameters["connectArcs"] = self.connectArcs - parameters["connectArcsFlip"] = self.connectArcsFlip - parameters["connectArcWithEdgeFlip"] = self.connectArcWithEdgeFlip - parameters["axisAngle"] = self.axisAngle - parameters["edgeAngle"] = self.edgeAngle - parameters["offset"] = self.offset - parameters["offset2"] = self.offset2 - parameters["ellipticFactor"] = self.ellipticFactor - parameters["rotateCenter"] = self.rotateCenter - return parameters - - def draw(self, context): - layout = self.layout - box = layout.box() - uiPercentage = 0.333 - - self.addEnumParameterToUI(box, False, uiPercentage, 'Mode:', 'workMode') - self.addEnumParameterToUI(box, False, uiPercentage, 'Plane:', 'planeEnum') - self.addEnumParameterToUI(box, False, uiPercentage, 'Reference:', 'referenceLocation') - - box = layout.box() - self.addEnumParameterToUI(box, False, uiPercentage, 'Scale base:', 'edgeScaleCenterEnum') - self.addParameterToUI(box, False, uiPercentage, 'Scale factor:', 'edgeScaleFactor') - - box = layout.box() - self.addEnumParameterToUI(box, False, uiPercentage, 'Entry mode:', 'entryMode') - - row = box.row(align=False) - row.prop(self, 'angleEnum', expand=True, text="Angle presets") - - disable_a = bool(self.entryMode == 'Angle' and self.angleEnum == 'Other') - disable_r = bool(self.entryMode == 'Radius') - - self.addParameterToUI(box, False, uiPercentage, 'Angle:', 'a', disable_a) - self.addParameterToUI(box, False, uiPercentage, 'Radius:', 'r', disable_r) - self.addParameterToUI(box, False, uiPercentage, 'Segments:', 'n') - - box = layout.box() - self.addCheckboxToUI(box, True, 'Options:', 'flip', 'invertAngle') - self.addCheckboxToUI(box, True, '', 'bothSides', 'fullCircles') - self.addCheckboxToUI(box, True, '', 'drawArcCenters') - - box = layout.box() - self.addCheckboxToUI(box, True, 'Remove:', 'removeEdges', 'removeScaledEdges') - - box = layout.box() - self.addCheckboxToUI(box, True, 'Connect:', 'connectArcs', 'connectArcsFlip') - self.addCheckboxToUI(box, True, '', 'connectArcWithEdge', 'connectArcWithEdgeFlip') - self.addCheckboxToUI(box, True, '', 'connectScaledAndBase') - - box = layout.box() - self.addParameterToUI(box, False, uiPercentage, 'Orhto offset:', 'offset') - self.addParameterToUI(box, False, uiPercentage, 'Parallel offset:', 'offset2') - - box = layout.box() - self.addParameterToUI(box, False, uiPercentage, 'Edge rotate :', 'edgeAngle') - self.addEnumParameterToUI(box, False, uiPercentage, 'Axis rotate center:', 'rotateCenter') - self.addParameterToUI(box, False, uiPercentage, 'Axis rotate:', 'axisAngle') - - box = layout.box() - self.addParameterToUI(box, False, uiPercentage, 'Elliptic factor:', 'ellipticFactor') - - def addParameterToUI(self, layout, alignment, percent, label, properties, disable=True): - row = layout.row(align=alignment) - split = row.split(factor=percent) - col = split.column() - - col.label(label) - col2 = split.column() - row = col2.row(align=alignment) - row.enabled = disable - row.prop(self, properties) - - def addCheckboxToUI(self, layout, alignment, label, property1, property2=None): - if label not in (""): - row = layout.row() - row.label(label) - row2 = layout.row(align=alignment) - if property2: - split = row2.split(factor=0.5) - split.prop(self, property1, toggle=True) - split.prop(self, property2, toggle=True) - else: - row2.prop(self, property1, toggle=True) - layout.separator() - - def addEnumParameterToUI(self, layout, alignment, percent, label, properties): - row = layout.row(align=alignment) - split = row.split(factor=percent) - col = split.column() - - col.label(label) - col2 = split.column() - row = col2.row(align=alignment) - row.prop(self, properties, expand=True, text="a") - - def execute(self, context): - - edges, mesh, bm = self.prepareMesh(context) - parameters = self.prepareParameters() - - self.resetValues(parameters["workMode"]) - - self.obj = context.view_layer.objects.active - scaledEdges = self.scaleDuplicatedEdges(bm, edges, parameters) - - if len(scaledEdges) > 0: - self.roundifyEdges(scaledEdges, parameters, bm, mesh) - - if parameters["connectScaledAndBase"]: - self.connectScaledEdgesWithBaseEdge(scaledEdges, edges, bm, mesh) - - self.sel.refreshMesh(bm, mesh) - self.selectEdgesAfterRoundifier(context, scaledEdges) - else: - debugPrintNew(True, "No edges selected!") - - if parameters["removeEdges"]: - bmesh.ops.delete(bm, geom=edges, context=2) - - if parameters["removeScaledEdges"] and self.edgeScaleFactor != 1.0: - bmesh.ops.delete(bm, geom=scaledEdges, context=2) - - bpy.ops.object.mode_set(mode='OBJECT') - bm.to_mesh(mesh) - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.mesh.remove_doubles() - - bm.free() - - return {'FINISHED'} - - def resetValues(self, workMode): - if workMode == "Reset": - self.setAllParamsToDefaults() - - def setAllParamsToDefaults(self): - try: - self.edgeScaleFactor = 1.0 - self.r = 1 - self.a = 180.0 - self.n = 4 - self.flip = False - self.invertAngle = False - self.fullCircles = False - self.bothSides = False - self.drawArcCenters = False - self.removeEdges = False - self.removeScaledEdges = False - - self.connectArcWithEdge = False - self.connectArcs = False - self.connectScaledAndBase = False - self.connectArcsFlip = False - self.connectArcWithEdgeFlip = False - - self.axisAngle = 0.0 - self.edgeAngle = 0.0 - self.offset = 0.0 - self.offset2 = 0.0 - self.ellipticFactor = 0.0 - - self.workMode = 'Normal' - self.entryMode = 'Angle' - self.angleEnum = '180' - self.referenceLocation = 'ORG' - self.planeEnum = 'XY' - self.edgeScaleCenterEnum = 'CENTER' - self.rotateCenter = 'Edge' - - self.report({'INFO'}, "The parameters have been reset to default values") - except Exception as e: - self.report({'WARNING'}, "The parameters could not be reset") - debugPrintNew(True, "\n[setAllParamsToDefaults]\n parameter reset error\n" + e) - - def scaleDuplicatedEdges(self, bm, edges, parameters): - scaleCenter = parameters["edgeScaleCenterEnum"] - factor = parameters["edgeScaleFactor"] - # this code is based on Zeffi's answer to my question - duplicateEdges = [] - if factor == 1: - duplicateEdges = edges - else: - for e in edges: - v1 = e.verts[0].co - v2 = e.verts[1].co - origin = None - if scaleCenter == 'CENTER': - origin = (v1 + v2) * 0.5 - elif scaleCenter == 'V1': - origin = v1 - elif scaleCenter == 'V2': - origin = v2 - - bmv1 = bm.verts.new(((v1 - origin) * factor) + origin) - bmv2 = bm.verts.new(((v2 - origin) * factor) + origin) - bme = bm.edges.new([bmv1, bmv2]) - duplicateEdges.append(bme) - return duplicateEdges - - def roundifyEdges(self, edges, parameters, bm, mesh): - arcs = [] - for e in edges: - arcVerts = self.roundify(e, parameters, bm, mesh) - arcs.append(arcVerts) - - if parameters["connectArcs"]: - self.connectArcsTogether(arcs, bm, mesh, parameters) - - def getNormalizedEdgeVector(self, edge): - V1 = edge.verts[0].co - V2 = edge.verts[1].co - edgeVector = V2 - V1 - normEdge = edgeVector.normalized() - return normEdge - - def getEdgePerpendicularVector(self, edge, plane): - normEdge = self.getNormalizedEdgeVector(edge) - - edgePerpendicularVector = Vector((normEdge[1], -normEdge[0], 0)) - if plane == YZ: - edgePerpendicularVector = Vector((0, normEdge[2], -normEdge[1])) - if plane == XZ: - edgePerpendicularVector = Vector((normEdge[2], 0, -normEdge[0])) - return edgePerpendicularVector - - def getEdgeInfo(self, edge): - V1 = edge.verts[0].co - V2 = edge.verts[1].co - edgeVector = V2 - V1 - edgeLength = edgeVector.length - edgeCenter = (V2 + V1) * 0.5 - return V1, V2, edgeVector, edgeLength, edgeCenter - - def roundify(self, edge, parameters, bm, mesh): - V1, V2, edgeVector, edgeLength, edgeCenter = self.getEdgeInfo(edge) - if self.skipThisEdge(V1, V2, parameters["plane"]): - return - - roundifyParams = None - arcVerts = None - roundifyParams = self.calculateRoundifyParams(edge, parameters, bm, mesh) - if roundifyParams is None: - return - - arcVerts = self.spinAndPostprocess(edge, parameters, bm, mesh, edgeCenter, roundifyParams) - return arcVerts - - def spinAndPostprocess(self, edge, parameters, bm, mesh, edgeCenter, roundifyParams): - spinnedVerts, roundifyParamsUpdated = self.drawSpin( - edge, edgeCenter, - roundifyParams, - parameters, bm, mesh - ) - postProcessedArcVerts = self.arcPostprocessing( - edge, parameters, bm, mesh, - roundifyParamsUpdated, - spinnedVerts, edgeCenter - ) - return postProcessedArcVerts - - def rotateArcAroundEdge(self, bm, mesh, arcVerts, parameters): - angle = parameters["edgeAngle"] - if angle != 0: - self.arc_rotator(arcVerts, angle, parameters) - - # arc_rotator method was created by PKHG, I (komi3D) adjusted it to fit the rest - def arc_rotator(self, arcVerts, extra_rotation, parameters): - bpy.ops.object.mode_set(mode='OBJECT') - old_location = self.obj.location.copy() - bpy.ops.transform.translate( - value=-old_location, - constraint_axis=(False, False, False), - orient_type='GLOBAL', - mirror=False, - use_proportional_edit=False, - ) - bpy.ops.object.mode_set(mode='EDIT') - adjust_matrix = self.obj.matrix_parent_inverse - bm = bmesh.from_edit_mesh(self.obj.data) - lastVert = len(arcVerts) - 1 - if parameters["drawArcCenters"]: - lastVert = lastVert - 1 # center gets added as last vert of arc - v0_old = adjust_matrix * arcVerts[0].co.copy() - - # PKHG>INFO move if necessary v0 to origin such that the axis gos through origin and v1 - if v0_old != Vector((0, 0, 0)): - for i, ele in enumerate(arcVerts): - arcVerts[i].co += - v0_old - - axis = arcVerts[0].co - arcVerts[lastVert].co - a_mat = Quaternion(axis, radians(extra_rotation)).normalized().to_matrix() - - for ele in arcVerts: - ele.co = a_mat * ele.co - - # PKHG>INFO move back if needed - if v0_old != Vector((0, 0, 0)): - for i, ele in enumerate(arcVerts): - arcVerts[i].co += + v0_old - - bpy.ops.object.mode_set(mode='OBJECT') - # PKHG>INFO move origin object back print("old location = " , old_location) - bpy.ops.transform.translate( - value=old_location, - constraint_axis=(False, False, False), - orient_type='GLOBAL', - mirror=False, - use_proportional_edit=False, - ) - bpy.ops.object.mode_set(mode='EDIT') - - def makeElliptic(self, bm, mesh, arcVertices, parameters): - if parameters["ellipticFactor"] != 0: # if 0 then nothing has to be done - lastVert = len(arcVertices) - 1 - if parameters["drawArcCenters"]: - lastVert = lastVert - 1 # center gets added as last vert of arc - v0co = arcVertices[0].co - v1co = arcVertices[lastVert].co - - for vertex in arcVertices: # range(len(res_list)): - # PKHg>INFO compute the base on the edge of the height-vector - top = vertex.co # res_list[nr].co - t = 0 - if v1co - v0co != 0: - t = (v1co - v0co).dot(top - v0co) / (v1co - v0co).length ** 2 - h_bottom = v0co + t * (v1co - v0co) - height = (h_bottom - top) - vertex.co = top + parameters["ellipticFactor"] * height - - return arcVertices - - def arcPostprocessing(self, edge, parameters, bm, mesh, roundifyParams, spinnedVerts, edgeCenter): - [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation] = roundifyParams - rotatedVerts = [] - if parameters["rotateCenter"] == 'Edge': - rotatedVerts = self.rotateArcAroundSpinAxis( - bm, mesh, spinnedVerts, parameters, edgeCenter - ) - elif parameters["rotateCenter"] == 'Spin': - rotatedVerts = self.rotateArcAroundSpinAxis( - bm, mesh, spinnedVerts, parameters, chosenSpinCenter - ) - elif parameters["rotateCenter"] == 'V1': - rotatedVerts = self.rotateArcAroundSpinAxis( - bm, mesh, spinnedVerts, parameters, edge.verts[0].co - ) - elif parameters["rotateCenter"] == 'V2': - rotatedVerts = self.rotateArcAroundSpinAxis( - bm, mesh, spinnedVerts, parameters, edge.verts[1].co - ) - - offsetVerts = self.offsetArcPerpendicular( - bm, mesh, rotatedVerts, edge, parameters - ) - offsetVerts2 = self.offsetArcParallel( - bm, mesh, offsetVerts, edge, parameters - ) - ellipticVerts = self.makeElliptic( - bm, mesh, offsetVerts2, parameters - ) - self.rotateArcAroundEdge(bm, mesh, ellipticVerts, parameters) - - if parameters["connectArcWithEdge"]: - self.connectArcTogetherWithEdge( - edge, offsetVerts2, bm, mesh, parameters - ) - return offsetVerts2 - - def connectArcTogetherWithEdge(self, edge, arcVertices, bm, mesh, parameters): - lastVert = len(arcVertices) - 1 - if parameters["drawArcCenters"]: - lastVert = lastVert - 1 # center gets added as last vert of arc - edgeV1 = edge.verts[0].co - edgeV2 = edge.verts[1].co - arcV1 = arcVertices[0].co - arcV2 = arcVertices[lastVert].co - - bmv1 = bm.verts.new(edgeV1) - bmv2 = bm.verts.new(arcV1) - - bmv3 = bm.verts.new(edgeV2) - bmv4 = bm.verts.new(arcV2) - - if parameters["connectArcWithEdgeFlip"] is False: - bme = bm.edges.new([bmv1, bmv2]) - bme2 = bm.edges.new([bmv3, bmv4]) - else: - bme = bm.edges.new([bmv1, bmv4]) - bme2 = bm.edges.new([bmv3, bmv2]) - self.sel.refreshMesh(bm, mesh) - - def connectScaledEdgesWithBaseEdge(self, scaledEdges, baseEdges, bm, mesh): - for i in range(0, len(scaledEdges)): - scaledEdgeV1 = scaledEdges[i].verts[0].co - baseEdgeV1 = baseEdges[i].verts[0].co - scaledEdgeV2 = scaledEdges[i].verts[1].co - baseEdgeV2 = baseEdges[i].verts[1].co - - bmv1 = bm.verts.new(baseEdgeV1) - bmv2 = bm.verts.new(scaledEdgeV1) - bme = bm.edges.new([bmv1, bmv2]) - - bmv3 = bm.verts.new(scaledEdgeV2) - bmv4 = bm.verts.new(baseEdgeV2) - bme = bm.edges.new([bmv3, bmv4]) - self.sel.refreshMesh(bm, mesh) - - def connectArcsTogether(self, arcs, bm, mesh, parameters): - for i in range(0, len(arcs) - 1): - # in case on XZ or YZ there are no arcs drawn - if arcs[i] is None or arcs[i + 1] is None: - return - - lastVert = len(arcs[i]) - 1 - if parameters["drawArcCenters"]: - lastVert = lastVert - 1 # center gets added as last vert of arc - # take last vert of arc i and first vert of arc i+1 - - V1 = arcs[i][lastVert].co - V2 = arcs[i + 1][0].co - - if parameters["connectArcsFlip"]: - V1 = arcs[i][0].co - V2 = arcs[i + 1][lastVert].co - - bmv1 = bm.verts.new(V1) - bmv2 = bm.verts.new(V2) - bme = bm.edges.new([bmv1, bmv2]) - - # connect last arc and first one - lastArcId = len(arcs) - 1 - lastVertIdOfLastArc = len(arcs[lastArcId]) - 1 - if parameters["drawArcCenters"]: - # center gets added as last vert of arc - lastVertIdOfLastArc = lastVertIdOfLastArc - 1 - - V1 = arcs[lastArcId][lastVertIdOfLastArc].co - V2 = arcs[0][0].co - if parameters["connectArcsFlip"]: - V1 = arcs[lastArcId][0].co - V2 = arcs[0][lastVertIdOfLastArc].co - - bmv1 = bm.verts.new(V1) - bmv2 = bm.verts.new(V2) - bme = bm.edges.new([bmv1, bmv2]) - - self.sel.refreshMesh(bm, mesh) - - def offsetArcPerpendicular(self, bm, mesh, Verts, edge, parameters): - perpendicularVector = self.getEdgePerpendicularVector(edge, parameters["plane"]) - offset = parameters["offset"] - translation = offset * perpendicularVector - - try: - bmesh.ops.translate(bm, verts=Verts, vec=translation) - except ValueError: - print("[Edge Roundifier]: Perpendicular translate value error - " - "multiple vertices in list - try unchecking 'Centers'") - - indexes = [v.index for v in Verts] - self.sel.refreshMesh(bm, mesh) - offsetVertices = [bm.verts[i] for i in indexes] - return offsetVertices - - def offsetArcParallel(self, bm, mesh, Verts, edge, parameters): - edgeVector = self.getNormalizedEdgeVector(edge) - offset = parameters["offset2"] - translation = offset * edgeVector - - try: - bmesh.ops.translate(bm, verts=Verts, vec=translation) - except ValueError: - print("[Edge Roundifier]: Parallel translate value error - " - "multiple vertices in list - try unchecking 'Centers'") - - indexes = [v.index for v in Verts] - self.sel.refreshMesh(bm, mesh) - offsetVertices = [bm.verts[i] for i in indexes] - return offsetVertices - - def skipThisEdge(self, V1, V2, plane): - # Check If It is possible to spin selected verts on this plane if not exit roundifier - if(plane == XY): - if (V1[0] == V2[0] and V1[1] == V2[1]): - return True - elif(plane == YZ): - if (V1[1] == V2[1] and V1[2] == V2[2]): - return True - elif(plane == XZ): - if (V1[0] == V2[0] and V1[2] == V2[2]): - return True - return False - - def calculateRoundifyParams(self, edge, parameters, bm, mesh): - # Because all data from mesh is in local coordinates - # and spin operator works on global coordinates - # We first need to translate all input data by vector equal - # to origin position and then perform calculations - # At least that is my understanding :) <komi3D> - - # V1 V2 stores Local Coordinates - V1, V2, edgeVector, edgeLength, edgeCenter = self.getEdgeInfo(edge) - - debugPrintNew(d_Plane, "PLANE: " + parameters["plane"]) - lineAB = self.calc.getLineCoefficientsPerpendicularToVectorInPoint( - edgeCenter, edgeVector, - parameters["plane"] - ) - circleMidPoint = V1 - circleMidPointOnPlane = self.calc.getCircleMidPointOnPlane( - V1, parameters["plane"] - ) - radius = parameters["radius"] - - angle = 0 - if (parameters["entryMode"] == 'Angle'): - if (parameters["angleEnum"] != 'Other'): - radius, angle = self.CalculateRadiusAndAngleForAnglePresets( - parameters["angleEnum"], radius, - angle, edgeLength - ) - else: - radius, angle = self.CalculateRadiusAndAngle(edgeLength) - debugPrintNew(d_Radius_Angle, "RADIUS = " + str(radius) + " ANGLE = " + str(angle)) - roots = None - if angle != pi: # mode other than 180 - if lineAB is None: - roots = self.calc.getLineCircleIntersectionsWhenXPerpendicular( - edgeCenter, circleMidPointOnPlane, - radius, parameters["plane"] - ) - else: - roots = self.calc.getLineCircleIntersections( - lineAB, circleMidPointOnPlane, radius - ) - - if roots is None: - debugPrintNew(True, - "[Edge Roundifier]: No centers were found. Change radius to higher value") - return None - roots = self.addMissingCoordinate(roots, V1, parameters["plane"]) # adds X, Y or Z coordinate - else: - roots = [edgeCenter, edgeCenter] - debugPrintNew(d_Roots, "roots=" + str(roots)) - - refObjectLocation = None - objectLocation = bpy.context.active_object.location # Origin Location - - if parameters["refObject"] == "ORG": - refObjectLocation = [0, 0, 0] - elif parameters["refObject"] == "CUR": - refObjectLocation = bpy.context.scene.cursor.location - objectLocation - else: - refObjectLocation = self.calc.getEdgeReference(edge, edgeCenter, parameters["plane"]) - - debugPrintNew(d_RefObject, parameters["refObject"], refObjectLocation) - chosenSpinCenter, otherSpinCenter = self.getSpinCenterClosestToRefCenter( - refObjectLocation, roots - ) - - if (parameters["entryMode"] == "Radius"): - halfAngle = self.calc.getAngle(edgeCenter, chosenSpinCenter, circleMidPoint) - angle = 2 * halfAngle[0] # in radians - self.a = degrees(angle) # in degrees - - spinAxis = self.getSpinAxis(parameters["plane"]) - steps = parameters["segments"] - angle = -angle # rotate clockwise by default - - return [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation] - - def drawSpin(self, edge, edgeCenter, roundifyParams, parameters, bm, mesh): - [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation] = roundifyParams - - v0org, v1org = (edge.verts[0], edge.verts[1]) - - if parameters["flip"]: - angle = -angle - spinCenterTemp = chosenSpinCenter - chosenSpinCenter = otherSpinCenter - otherSpinCenter = spinCenterTemp - - if(parameters["invertAngle"]): - if angle < 0: - angle = two_pi + angle - elif angle > 0: - angle = -two_pi + angle - else: - angle = two_pi - - if(parameters["fullCircles"]): - angle = two_pi - - v0 = bm.verts.new(v0org.co) - - result = bmesh.ops.spin( - bm, geom=[v0], cent=chosenSpinCenter, axis=spinAxis, - angle=angle, steps=steps, use_duplicate=False - ) - - # it seems there is something wrong with last index of this spin - # I need to calculate the last index manually here - vertsLength = len(bm.verts) - bm.verts.ensure_lookup_table() - lastVertIndex = bm.verts[vertsLength - 1].index - lastSpinVertIndices = self.getLastSpinVertIndices(steps, lastVertIndex) - - self.sel.refreshMesh(bm, mesh) - - alternativeLastSpinVertIndices = [] - bothSpinVertices = [] - spinVertices = [] - alternate = False - - if ((angle == pi or angle == -pi) and not parameters["bothSides"]): - - midVertexIndex = lastVertIndex - round(steps / 2) - bm.verts.ensure_lookup_table() - midVert = bm.verts[midVertexIndex].co - - midVertexDistance = (Vector(refObjectLocation) - Vector(midVert)).length - midEdgeDistance = (Vector(refObjectLocation) - Vector(edgeCenter)).length - - if ((parameters["invertAngle"]) or (parameters["flip"])): - if (midVertexDistance > midEdgeDistance): - alternativeLastSpinVertIndices = self.alternateSpin( - bm, mesh, angle, chosenSpinCenter, - spinAxis, steps, v0, v1org, lastSpinVertIndices - ) - else: - if (midVertexDistance < midEdgeDistance): - alternativeLastSpinVertIndices = self.alternateSpin( - bm, mesh, angle, chosenSpinCenter, - spinAxis, steps, v0, v1org, lastSpinVertIndices - ) - elif (angle != two_pi): # to allow full circles - if (result['geom_last'][0].co - v1org.co).length > SPIN_END_THRESHOLD: - alternativeLastSpinVertIndices = self.alternateSpin( - bm, mesh, angle, chosenSpinCenter, - spinAxis, steps, v0, v1org, lastSpinVertIndices - ) - alternate = True - - self.sel.refreshMesh(bm, mesh) - if alternativeLastSpinVertIndices != []: - lastSpinVertIndices = alternativeLastSpinVertIndices - - if lastSpinVertIndices.stop <= len(bm.verts): # make sure arc was added to bmesh - spinVertices = [bm.verts[i] for i in lastSpinVertIndices] - if alternativeLastSpinVertIndices != []: - spinVertices = spinVertices + [v0] - else: - spinVertices = [v0] + spinVertices - - if (parameters["bothSides"]): - # do some more testing here!!! - if (angle == pi or angle == -pi): - alternativeLastSpinVertIndices = self.alternateSpinNoDelete( - bm, mesh, -angle, chosenSpinCenter, - spinAxis, steps, v0, v1org, [] - ) - elif alternate: - alternativeLastSpinVertIndices = self.alternateSpinNoDelete( - bm, mesh, angle, otherSpinCenter, - spinAxis, steps, v0, v1org, [] - ) - elif not alternate: - alternativeLastSpinVertIndices = self.alternateSpinNoDelete( - bm, mesh, -angle, otherSpinCenter, - spinAxis, steps, v0, v1org, [] - ) - bothSpinVertices = [bm.verts[i] for i in lastSpinVertIndices] - alternativeSpinVertices = [bm.verts[i] for i in alternativeLastSpinVertIndices] - bothSpinVertices = [v0] + bothSpinVertices + alternativeSpinVertices - spinVertices = bothSpinVertices - - if (parameters["fullCircles"]): - v1 = bm.verts.new(v1org.co) - spinVertices = spinVertices + [v1] - - if (parameters['drawArcCenters']): - centerVert = bm.verts.new(chosenSpinCenter) - spinVertices.append(centerVert) - - return spinVertices, [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation] - - def deleteSpinVertices(self, bm, mesh, lastSpinVertIndices): - verticesForDeletion = [] - bm.verts.ensure_lookup_table() - for i in lastSpinVertIndices: - vi = bm.verts[i] - vi.select = True - debugPrintNew(True, str(i) + ") " + str(vi)) - verticesForDeletion.append(vi) - - bmesh.ops.delete(bm, geom=verticesForDeletion, context=1) - bmesh.update_edit_mesh(mesh, True) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') - - def alternateSpinNoDelete(self, bm, mesh, angle, chosenSpinCenter, - spinAxis, steps, v0, v1org, lastSpinVertIndices): - v0prim = v0 - - result2 = bmesh.ops.spin(bm, geom=[v0prim], cent=chosenSpinCenter, axis=spinAxis, - angle=angle, steps=steps, use_duplicate=False) - vertsLength = len(bm.verts) - bm.verts.ensure_lookup_table() - lastVertIndex2 = bm.verts[vertsLength - 1].index - - lastSpinVertIndices2 = self.getLastSpinVertIndices(steps, lastVertIndex2) - return lastSpinVertIndices2 - - def alternateSpin(self, bm, mesh, angle, chosenSpinCenter, - spinAxis, steps, v0, v1org, lastSpinVertIndices): - - self.deleteSpinVertices(bm, mesh, lastSpinVertIndices) - v0prim = v0 - - result2 = bmesh.ops.spin( - bm, geom=[v0prim], cent=chosenSpinCenter, axis=spinAxis, - angle=-angle, steps=steps, use_duplicate=False - ) - # it seems there is something wrong with last index of this spin - # I need to calculate the last index manually here - vertsLength = len(bm.verts) - bm.verts.ensure_lookup_table() - lastVertIndex2 = bm.verts[vertsLength - 1].index - - lastSpinVertIndices2 = self.getLastSpinVertIndices(steps, lastVertIndex2) - # second spin also does not hit the v1org - if (result2['geom_last'][0].co - v1org.co).length > SPIN_END_THRESHOLD: - - self.deleteSpinVertices(bm, mesh, lastSpinVertIndices2) - self.deleteSpinVertices(bm, mesh, range(v0.index, v0.index + 1)) - return [] - else: - return lastSpinVertIndices2 - - def getLastSpinVertIndices(self, steps, lastVertIndex): - arcfirstVertexIndex = lastVertIndex - steps + 1 - lastSpinVertIndices = range(arcfirstVertexIndex, lastVertIndex + 1) - return lastSpinVertIndices - - def rotateArcAroundSpinAxis(self, bm, mesh, vertices, parameters, edgeCenter): - axisAngle = parameters["axisAngle"] - plane = parameters["plane"] - # compensate rotation center - objectLocation = bpy.context.active_object.location - center = objectLocation + edgeCenter - - rot = Euler((0.0, 0.0, radians(axisAngle)), 'XYZ').to_matrix() - if plane == YZ: - rot = Euler((radians(axisAngle), 0.0, 0.0), 'XYZ').to_matrix() - if plane == XZ: - rot = Euler((0.0, radians(axisAngle), 0.0), 'XYZ').to_matrix() - - indexes = [v.index for v in vertices] - - bmesh.ops.rotate( - bm, - cent=center, - matrix=rot, - verts=vertices, - space=bpy.context.edit_object.matrix_world - ) - self.sel.refreshMesh(bm, mesh) - bm.verts.ensure_lookup_table() - rotatedVertices = [bm.verts[i] for i in indexes] - - return rotatedVertices - - def CalculateRadiusAndAngle(self, edgeLength): - degAngle = self.a - angle = radians(degAngle) - self.r = radius = edgeLength / (2 * sin(angle / 2)) - return radius, angle - - def CalculateRadiusAndAngleForAnglePresets(self, angleEnum, initR, initA, edgeLength): - radius = initR - angle = initA - try: - # Note - define an integer string in the angleEnum - angle_convert = int(angleEnum) - self.a = angle_convert - except: - self.a = 180 # fallback - debugPrintNew(True, - "CalculateRadiusAndAngleForAnglePresets problem with int conversion") - - return self.CalculateRadiusAndAngle(edgeLength) - - def getSpinCenterClosestToRefCenter(self, objLocation, roots): - root0Distance = (Vector(objLocation) - Vector(roots[0])).length - root1Distance = (Vector(objLocation) - Vector(roots[1])).length - - chosenId = 0 - rejectedId = 1 - if (root0Distance > root1Distance): - chosenId = 1 - rejectedId = 0 - return roots[chosenId], roots[rejectedId] - - def addMissingCoordinate(self, roots, startVertex, plane): - if roots is not None: - a, b = roots[0] - c, d = roots[1] - if plane == XY: - roots[0] = Vector((a, b, startVertex[2])) - roots[1] = Vector((c, d, startVertex[2])) - if plane == YZ: - roots[0] = Vector((startVertex[0], a, b)) - roots[1] = Vector((startVertex[0], c, d)) - if plane == XZ: - roots[0] = Vector((a, startVertex[1], b)) - roots[1] = Vector((c, startVertex[1], d)) - return roots - - def selectEdgesAfterRoundifier(self, context, edges): - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') - mesh = context.view_layer.objects.active.data - bmnew = bmesh.new() - bmnew.from_mesh(mesh) - - self.deselectEdges(bmnew) - for selectedEdge in edges: - for e in bmnew.edges: - if (e.verts[0].co - selectedEdge.verts[0].co).length <= self.threshold \ - and (e.verts[1].co - selectedEdge.verts[1].co).length <= self.threshold: - e.select_set(True) - - bpy.ops.object.mode_set(mode='OBJECT') - bmnew.to_mesh(mesh) - bmnew.free() - bpy.ops.object.mode_set(mode='EDIT') - - def deselectEdges(self, bm): - for edge in bm.edges: - edge.select_set(False) - - def getSpinAxis(self, plane): - axis = (0, 0, 1) - if plane == YZ: - axis = (1, 0, 0) - if plane == XZ: - axis = (0, 1, 0) - return axis - - -def register(): - bpy.utils.register_class(EdgeRoundifier) - - -def unregister(): - bpy.utils.unregister_class(EdgeRoundifier) - - -if __name__ == "__main__": - register() diff --git a/mesh_extra_tools/mesh_edges_floor_plan.py b/mesh_extra_tools/mesh_edges_floor_plan.py deleted file mode 100644 index 1804c79aa..000000000 --- a/mesh_extra_tools/mesh_edges_floor_plan.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_edges_length.py b/mesh_extra_tools/mesh_edges_length.py deleted file mode 100644 index d8c3ec5f2..000000000 --- a/mesh_extra_tools/mesh_edges_length.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_edgetools.py b/mesh_extra_tools/mesh_edgetools.py deleted file mode 100644 index 0547e915f..000000000 --- a/mesh_extra_tools/mesh_edgetools.py +++ /dev/null @@ -1,1878 +0,0 @@ -# The Blender Edgetools is to bring CAD tools to Blender. -# Copyright (C) 2012 Paul Marshall - -# ##### 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 ##### - -# <pep8 compliant> - -bl_info = { - "name": "EdgeTools", - "author": "Paul Marshall", - "version": (0, 9, 2), - "blender": (2, 68, 0), - "location": "View3D > Toolbar and View3D > Specials (W-key)", - "warning": "", - "description": "CAD style edge manipulation tools", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Modeling/EdgeTools", - "category": "Mesh"} - - -import bpy -import bmesh -from bpy.types import ( - Operator, - Menu, - ) -from math import acos, pi, radians, sqrt -from mathutils import Matrix, Vector -from mathutils.geometry import ( - distance_point_to_plane, - interpolate_bezier, - intersect_point_line, - intersect_line_line, - intersect_line_plane, - ) -from bpy.props import ( - BoolProperty, - IntProperty, - FloatProperty, - EnumProperty, - ) - -""" -Blender EdgeTools -This is a toolkit for edge manipulation based on mesh manipulation -abilities of several CAD/CAE packages, notably CATIA's Geometric Workbench -from which most of these tools have a functional basis. - -The GUI and Blender add-on structure shamelessly coded in imitation of the -LoopTools addon. - -Examples: -- "Ortho" inspired from CATIA's line creation tool which creates a line of a - user specified length at a user specified angle to a curve at a chosen - point. The user then selects the plane the line is to be created in. -- "Shaft" is inspired from CATIA's tool of the same name. However, instead - of a curve around an axis, this will instead shaft a line, a point, or - a fixed radius about the selected axis. -- "Slice" is from CATIA's ability to split a curve on a plane. When - completed this be a Python equivalent with all the same basic - functionality, though it will sadly be a little clumsier to use due - to Blender's selection limitations. - -Notes: -- Fillet operator and related functions removed as they didn't work -- Buggy parts have been hidden behind ENABLE_DEBUG global (set it to True) - Example: Shaft with more than two edges selected - -Paul "BrikBot" Marshall -Created: January 28, 2012 -Last Modified: October 6, 2012 - -Coded in IDLE, tested in Blender 2.6. -Search for "@todo" to quickly find sections that need work - -Note: lijenstina - modified this script in preparation for merging -fixed the needless jumping to object mode for bmesh creation -causing the crash with the Slice > Rip operator -Removed the test operator since version 0.9.2 -added general error handling -""" - -# Enable debug -# Set to True to have the debug prints available -ENABLE_DEBUG = False - - -# Quick an dirty method for getting the sign of a number: -def sign(number): - return (number > 0) - (number < 0) - - -# is_parallel -# Checks to see if two lines are parallel - -def is_parallel(v1, v2, v3, v4): - result = intersect_line_line(v1, v2, v3, v4) - return result is None - - -# Handle error notifications -def error_handlers(self, op_name, error, reports="ERROR", func=False): - if self and reports: - self.report({'WARNING'}, reports + " (See Console for more info)") - - is_func = "Function" if func else "Operator" - print("\n[Mesh EdgeTools]\n{}: {}\nError: {}\n".format(is_func, op_name, error)) - - -def flip_edit_mode(): - bpy.ops.object.editmode_toggle() - bpy.ops.object.editmode_toggle() - - -# check the appropriate selection condition -# to prevent crashes with the index out of range errors -# pass the bEdges and bVerts based selection tables here -# types: Edge, Vertex, All -def is_selected_enough(self, bEdges, bVerts, edges_n=1, verts_n=0, types="Edge"): - check = False - try: - if bEdges and types == "Edge": - check = (len(bEdges) >= edges_n) - elif bVerts and types == "Vertex": - check = (len(bVerts) >= verts_n) - elif bEdges and bVerts and types == "All": - check = (len(bEdges) >= edges_n and len(bVerts) >= verts_n) - - if check is False: - strings = "%s Vertices and / or " % verts_n if verts_n != 0 else "" - self.report({'WARNING'}, - "Needs at least " + strings + "%s Edge(s) selected. " - "Operation Cancelled" % edges_n) - flip_edit_mode() - - return check - - except Exception as e: - error_handlers(self, "is_selected_enough", e, - "No appropriate selection. Operation Cancelled", func=True) - return False - - return False - - -# is_axial -# This is for the special case where the edge is parallel to an axis. -# The projection onto the XY plane will fail so it will have to be handled differently - -def is_axial(v1, v2, error=0.000002): - vector = v2 - v1 - # Don't need to store, but is easier to read: - vec0 = vector[0] > -error and vector[0] < error - vec1 = vector[1] > -error and vector[1] < error - vec2 = vector[2] > -error and vector[2] < error - if (vec0 or vec1) and vec2: - return 'Z' - elif vec0 and vec1: - return 'Y' - return None - - -# is_same_co -# For some reason "Vector = Vector" does not seem to look at the actual coordinates - -def is_same_co(v1, v2): - if len(v1) != len(v2): - return False - else: - for co1, co2 in zip(v1, v2): - if co1 != co2: - return False - return True - - -def is_face_planar(face, error=0.0005): - for v in face.verts: - d = distance_point_to_plane(v.co, face.verts[0].co, face.normal) - if ENABLE_DEBUG: - print("Distance: " + str(d)) - if d < -error or d > error: - return False - return True - - -# other_joined_edges -# Starts with an edge. Then scans for linked, selected edges and builds a -# list with them in "order", starting at one end and moving towards the other - -def order_joined_edges(edge, edges=[], direction=1): - if len(edges) == 0: - edges.append(edge) - edges[0] = edge - - if ENABLE_DEBUG: - print(edge, end=", ") - print(edges, end=", ") - print(direction, end="; ") - - # Robustness check: direction cannot be zero - if direction == 0: - direction = 1 - - newList = [] - for e in edge.verts[0].link_edges: - if e.select and edges.count(e) == 0: - if direction > 0: - edges.insert(0, e) - newList.extend(order_joined_edges(e, edges, direction + 1)) - newList.extend(edges) - else: - edges.append(e) - newList.extend(edges) - newList.extend(order_joined_edges(e, edges, direction - 1)) - - # This will only matter at the first level: - direction = direction * -1 - - for e in edge.verts[1].link_edges: - if e.select and edges.count(e) == 0: - if direction > 0: - edges.insert(0, e) - newList.extend(order_joined_edges(e, edges, direction + 2)) - newList.extend(edges) - else: - edges.append(e) - newList.extend(edges) - newList.extend(order_joined_edges(e, edges, direction)) - - if ENABLE_DEBUG: - print(newList, end=", ") - print(direction) - - return newList - - -# --------------- GEOMETRY CALCULATION METHODS -------------- - -# distance_point_line -# I don't know why the mathutils.geometry API does not already have this, but -# it is trivial to code using the structures already in place. Instead of -# returning a float, I also want to know the direction vector defining the -# distance. Distance can be found with "Vector.length" - -def distance_point_line(pt, line_p1, line_p2): - int_co = intersect_point_line(pt, line_p1, line_p2) - distance_vector = int_co[0] - pt - return distance_vector - - -# interpolate_line_line -# This is an experiment into a cubic Hermite spline (c-spline) for connecting -# two edges with edges that obey the general equation. -# This will return a set of point coordinates (Vectors) -# -# A good, easy to read background on the mathematics can be found at: -# http://cubic.org/docs/hermite.htm -# -# Right now this is . . . less than functional :P -# @todo -# - C-Spline and Bezier curves do not end on p2_co as they are supposed to. -# - B-Spline just fails. Epically. -# - Add more methods as I come across them. Who said flexibility was bad? - -def interpolate_line_line(p1_co, p1_dir, p2_co, p2_dir, segments, tension=1, - typ='BEZIER', include_ends=False): - pieces = [] - fraction = 1 / segments - - # Form: p1, tangent 1, p2, tangent 2 - if typ == 'HERMITE': - poly = [[2, -3, 0, 1], [1, -2, 1, 0], - [-2, 3, 0, 0], [1, -1, 0, 0]] - elif typ == 'BEZIER': - poly = [[-1, 3, -3, 1], [3, -6, 3, 0], - [1, 0, 0, 0], [-3, 3, 0, 0]] - p1_dir = p1_dir + p1_co - p2_dir = -p2_dir + p2_co - elif typ == 'BSPLINE': - # Supposed poly matrix for a cubic b-spline: - # poly = [[-1, 3, -3, 1], [3, -6, 3, 0], - # [-3, 0, 3, 0], [1, 4, 1, 0]] - # My own invention to try to get something that somewhat acts right - # This is semi-quadratic rather than fully cubic: - poly = [[0, -1, 0, 1], [1, -2, 1, 0], - [0, -1, 2, 0], [1, -1, 0, 0]] - - if include_ends: - pieces.append(p1_co) - - # Generate each point: - for i in range(segments - 1): - t = fraction * (i + 1) - if ENABLE_DEBUG: - print(t) - s = [t ** 3, t ** 2, t, 1] - h00 = (poly[0][0] * s[0]) + (poly[0][1] * s[1]) + (poly[0][2] * s[2]) + (poly[0][3] * s[3]) - h01 = (poly[1][0] * s[0]) + (poly[1][1] * s[1]) + (poly[1][2] * s[2]) + (poly[1][3] * s[3]) - h10 = (poly[2][0] * s[0]) + (poly[2][1] * s[1]) + (poly[2][2] * s[2]) + (poly[2][3] * s[3]) - h11 = (poly[3][0] * s[0]) + (poly[3][1] * s[1]) + (poly[3][2] * s[2]) + (poly[3][3] * s[3]) - pieces.append((h00 * p1_co) + (h01 * p1_dir) + (h10 * p2_co) + (h11 * p2_dir)) - if include_ends: - pieces.append(p2_co) - - # Return: - if len(pieces) == 0: - return None - else: - if ENABLE_DEBUG: - print(pieces) - return pieces - - -# intersect_line_face - -# Calculates the coordinate of intersection of a line with a face. It returns -# the coordinate if one exists, otherwise None. It can only deal with tris or -# quads for a face. A quad does NOT have to be planar -""" -Quad math and theory: -A quad may not be planar. Therefore the treated definition of the surface is -that the surface is composed of all lines bridging two other lines defined by -the given four points. The lines do not "cross" - -The two lines in 3-space can defined as: -┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐ -│x1│ │a11│ │b11│ │x2│ │a21│ │b21│ -│y1│ = (1-t1)│a12│ + t1│b12│, │y2│ = (1-t2)│a22│ + t2│b22│ -│z1│ │a13│ │b13│ │z2│ │a23│ │b23│ -└ ┘ └ ┘ └ ┘ └ ┘ └ ┘ └ ┘ -Therefore, the surface is the lines defined by every point alone the two -lines with a same "t" value (t1 = t2). This is basically R = V1 + tQ, where -Q = V2 - V1 therefore R = V1 + t(V2 - V1) -> R = (1 - t)V1 + tV2: -┌ ┐ ┌ ┐ ┌ ┐ -│x12│ │(1-t)a11 + t * b11│ │(1-t)a21 + t * b21│ -│y12│ = (1 - t12)│(1-t)a12 + t * b12│ + t12│(1-t)a22 + t * b22│ -│z12│ │(1-t)a13 + t * b13│ │(1-t)a23 + t * b23│ -└ ┘ └ ┘ └ ┘ -Now, the equation of our line can be likewise defined: -┌ ┐ ┌ ┐ ┌ ┐ -│x3│ │a31│ │b31│ -│y3│ = │a32│ + t3│b32│ -│z3│ │a33│ │b33│ -└ ┘ └ ┘ └ ┘ -Now we just have to find a valid solution for the two equations. This should -be our point of intersection. Therefore, x12 = x3 -> x, y12 = y3 -> y, -z12 = z3 -> z. Thus, to find that point we set the equation defining the -surface as equal to the equation for the line: - ┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐ - │(1-t)a11 + t * b11│ │(1-t)a21 + t * b21│ │a31│ │b31│ -(1 - t12)│(1-t)a12 + t * b12│ + t12│(1-t)a22 + t * b22│ = │a32│ + t3│b32│ - │(1-t)a13 + t * b13│ │(1-t)a23 + t * b23│ │a33│ │b33│ - └ ┘ └ ┘ └ ┘ └ ┘ -This leaves us with three equations, three unknowns. Solving the system by -hand is practically impossible, but using Mathematica we are given an insane -series of three equations (not reproduced here for the sake of space: see -http://www.mediafire.com/file/cc6m6ba3sz2b96m/intersect_line_surface.nb and -http://www.mediafire.com/file/0egbr5ahg14talm/intersect_line_surface2.nb for -Mathematica computation). - -Additionally, the resulting series of equations may result in a div by zero -exception if the line in question if parallel to one of the axis or if the -quad is planar and parallel to either the XY, XZ, or YZ planes. However, the -system is still solvable but must be dealt with a little differently to avaid -these special cases. Because the resulting equations are a little different, -we have to code them differently. 00Hence the special cases. - -Tri math and theory: -A triangle must be planar (three points define a plane). So we just -have to make sure that the line intersects inside the triangle. - -If the point is within the triangle, then the angle between the lines that -connect the point to the each individual point of the triangle will be -equal to 2 * PI. Otherwise, if the point is outside the triangle, then the -sum of the angles will be less. -""" -# @todo -# - Figure out how to deal with n-gons -# How the heck is a face with 8 verts defined mathematically? -# How do I then find the intersection point of a line with said vert? -# How do I know if that point is "inside" all the verts? -# I have no clue, and haven't been able to find anything on it so far -# Maybe if someone (actually reads this and) who knows could note? - - -def intersect_line_face(edge, face, is_infinite=False, error=0.000002): - int_co = None - - # If we are dealing with a non-planar quad: - if len(face.verts) == 4 and not is_face_planar(face): - edgeA = face.edges[0] - edgeB = None - flipB = False - - for i in range(len(face.edges)): - if face.edges[i].verts[0] not in edgeA.verts and \ - face.edges[i].verts[1] not in edgeA.verts: - - edgeB = face.edges[i] - break - - # I haven't figured out a way to mix this in with the above. Doing so might remove a - # few extra instructions from having to be executed saving a few clock cycles: - for i in range(len(face.edges)): - if face.edges[i] == edgeA or face.edges[i] == edgeB: - continue - if ((edgeA.verts[0] in face.edges[i].verts and - edgeB.verts[1] in face.edges[i].verts) or - (edgeA.verts[1] in face.edges[i].verts and edgeB.verts[0] in face.edges[i].verts)): - - flipB = True - break - - # Define calculation coefficient constants: - # "xx1" is the x coordinate, "xx2" is the y coordinate, and "xx3" is the z coordinate - a11, a12, a13 = edgeA.verts[0].co[0], edgeA.verts[0].co[1], edgeA.verts[0].co[2] - b11, b12, b13 = edgeA.verts[1].co[0], edgeA.verts[1].co[1], edgeA.verts[1].co[2] - - if flipB: - a21, a22, a23 = edgeB.verts[1].co[0], edgeB.verts[1].co[1], edgeB.verts[1].co[2] - b21, b22, b23 = edgeB.verts[0].co[0], edgeB.verts[0].co[1], edgeB.verts[0].co[2] - else: - a21, a22, a23 = edgeB.verts[0].co[0], edgeB.verts[0].co[1], edgeB.verts[0].co[2] - b21, b22, b23 = edgeB.verts[1].co[0], edgeB.verts[1].co[1], edgeB.verts[1].co[2] - a31, a32, a33 = edge.verts[0].co[0], edge.verts[0].co[1], edge.verts[0].co[2] - b31, b32, b33 = edge.verts[1].co[0], edge.verts[1].co[1], edge.verts[1].co[2] - - # There are a bunch of duplicate "sub-calculations" inside the resulting - # equations for t, t12, and t3. Calculate them once and store them to - # reduce computational time: - m01 = a13 * a22 * a31 - m02 = a12 * a23 * a31 - m03 = a13 * a21 * a32 - m04 = a11 * a23 * a32 - m05 = a12 * a21 * a33 - m06 = a11 * a22 * a33 - m07 = a23 * a32 * b11 - m08 = a22 * a33 * b11 - m09 = a23 * a31 * b12 - m10 = a21 * a33 * b12 - m11 = a22 * a31 * b13 - m12 = a21 * a32 * b13 - m13 = a13 * a32 * b21 - m14 = a12 * a33 * b21 - m15 = a13 * a31 * b22 - m16 = a11 * a33 * b22 - m17 = a12 * a31 * b23 - m18 = a11 * a32 * b23 - m19 = a13 * a22 * b31 - m20 = a12 * a23 * b31 - m21 = a13 * a32 * b31 - m22 = a23 * a32 * b31 - m23 = a12 * a33 * b31 - m24 = a22 * a33 * b31 - m25 = a23 * b12 * b31 - m26 = a33 * b12 * b31 - m27 = a22 * b13 * b31 - m28 = a32 * b13 * b31 - m29 = a13 * b22 * b31 - m30 = a33 * b22 * b31 - m31 = a12 * b23 * b31 - m32 = a32 * b23 * b31 - m33 = a13 * a21 * b32 - m34 = a11 * a23 * b32 - m35 = a13 * a31 * b32 - m36 = a23 * a31 * b32 - m37 = a11 * a33 * b32 - m38 = a21 * a33 * b32 - m39 = a23 * b11 * b32 - m40 = a33 * b11 * b32 - m41 = a21 * b13 * b32 - m42 = a31 * b13 * b32 - m43 = a13 * b21 * b32 - m44 = a33 * b21 * b32 - m45 = a11 * b23 * b32 - m46 = a31 * b23 * b32 - m47 = a12 * a21 * b33 - m48 = a11 * a22 * b33 - m49 = a12 * a31 * b33 - m50 = a22 * a31 * b33 - m51 = a11 * a32 * b33 - m52 = a21 * a32 * b33 - m53 = a22 * b11 * b33 - m54 = a32 * b11 * b33 - m55 = a21 * b12 * b33 - m56 = a31 * b12 * b33 - m57 = a12 * b21 * b33 - m58 = a32 * b21 * b33 - m59 = a11 * b22 * b33 - m60 = a31 * b22 * b33 - m61 = a33 * b12 * b21 - m62 = a32 * b13 * b21 - m63 = a33 * b11 * b22 - m64 = a31 * b13 * b22 - m65 = a32 * b11 * b23 - m66 = a31 * b12 * b23 - m67 = b13 * b22 * b31 - m68 = b12 * b23 * b31 - m69 = b13 * b21 * b32 - m70 = b11 * b23 * b32 - m71 = b12 * b21 * b33 - m72 = b11 * b22 * b33 - n01 = m01 - m02 - m03 + m04 + m05 - m06 - n02 = -m07 + m08 + m09 - m10 - m11 + m12 + m13 - m14 - m15 + m16 + m17 - m18 - \ - m25 + m27 + m29 - m31 + m39 - m41 - m43 + m45 - m53 + m55 + m57 - m59 - n03 = -m19 + m20 + m33 - m34 - m47 + m48 - n04 = m21 - m22 - m23 + m24 - m35 + m36 + m37 - m38 + m49 - m50 - m51 + m52 - n05 = m26 - m28 - m30 + m32 - m40 + m42 + m44 - m46 + m54 - m56 - m58 + m60 - n06 = m61 - m62 - m63 + m64 + m65 - m66 - m67 + m68 + m69 - m70 - m71 + m72 - n07 = 2 * n01 + n02 + 2 * n03 + n04 + n05 - n08 = n01 + n02 + n03 + n06 - - # Calculate t, t12, and t3: - t = (n07 - sqrt(pow(-n07, 2) - 4 * (n01 + n03 + n04) * n08)) / (2 * n08) - - # t12 can be greatly simplified by defining it with t in it: - # If block used to help prevent any div by zero error. - t12 = 0 - - if a31 == b31: - # The line is parallel to the z-axis: - if a32 == b32: - t12 = ((a11 - a31) + (b11 - a11) * t) / ((a21 - a11) + (a11 - a21 - b11 + b21) * t) - # The line is parallel to the y-axis: - elif a33 == b33: - t12 = ((a11 - a31) + (b11 - a11) * t) / ((a21 - a11) + (a11 - a21 - b11 + b21) * t) - # The line is along the y/z-axis but is not parallel to either: - else: - t12 = -(-(a33 - b33) * (-a32 + a12 * (1 - t) + b12 * t) + (a32 - b32) * - (-a33 + a13 * (1 - t) + b13 * t)) / (-(a33 - b33) * - ((a22 - a12) * (1 - t) + (b22 - b12) * t) + (a32 - b32) * - ((a23 - a13) * (1 - t) + (b23 - b13) * t)) - elif a32 == b32: - # The line is parallel to the x-axis: - if a33 == b33: - t12 = ((a12 - a32) + (b12 - a12) * t) / ((a22 - a12) + (a12 - a22 - b12 + b22) * t) - # The line is along the x/z-axis but is not parallel to either: - else: - t12 = -(-(a33 - b33) * (-a31 + a11 * (1 - t) + b11 * t) + (a31 - b31) * (-a33 + a13 * - (1 - t) + b13 * t)) / (-(a33 - b33) * ((a21 - a11) * (1 - t) + (b21 - b11) * t) + - (a31 - b31) * ((a23 - a13) * (1 - t) + (b23 - b13) * t)) - # The line is along the x/y-axis but is not parallel to either: - else: - t12 = -(-(a32 - b32) * (-a31 + a11 * (1 - t) + b11 * t) + (a31 - b31) * (-a32 + a12 * - (1 - t) + b12 * t)) / (-(a32 - b32) * ((a21 - a11) * (1 - t) + (b21 - b11) * t) + - (a31 - b31) * ((a22 - a21) * (1 - t) + (b22 - b12) * t)) - - # Likewise, t3 is greatly simplified by defining it in terms of t and t12: - # If block used to prevent a div by zero error. - t3 = 0 - if a31 != b31: - t3 = (-a11 + a31 + (a11 - b11) * t + (a11 - a21) * - t12 + (a21 - a11 + b11 - b21) * t * t12) / (a31 - b31) - elif a32 != b32: - t3 = (-a12 + a32 + (a12 - b12) * t + (a12 - a22) * - t12 + (a22 - a12 + b12 - b22) * t * t12) / (a32 - b32) - elif a33 != b33: - t3 = (-a13 + a33 + (a13 - b13) * t + (a13 - a23) * - t12 + (a23 - a13 + b13 - b23) * t * t12) / (a33 - b33) - else: - if ENABLE_DEBUG: - print("The second edge is a zero-length edge") - return None - - # Calculate the point of intersection: - x = (1 - t3) * a31 + t3 * b31 - y = (1 - t3) * a32 + t3 * b32 - z = (1 - t3) * a33 + t3 * b33 - int_co = Vector((x, y, z)) - - if ENABLE_DEBUG: - print(int_co) - - # If the line does not intersect the quad, we return "None": - if (t < -1 or t > 1 or t12 < -1 or t12 > 1) and not is_infinite: - int_co = None - - elif len(face.verts) == 3: - p1, p2, p3 = face.verts[0].co, face.verts[1].co, face.verts[2].co - int_co = intersect_line_plane(edge.verts[0].co, edge.verts[1].co, p1, face.normal) - - # Only check if the triangle is not being treated as an infinite plane: - # Math based from http://paulbourke.net/geometry/linefacet/ - if int_co is not None and not is_infinite: - pA = p1 - int_co - pB = p2 - int_co - pC = p3 - int_co - # These must be unit vectors, else we risk a domain error: - pA.length = 1 - pB.length = 1 - pC.length = 1 - aAB = acos(pA.dot(pB)) - aBC = acos(pB.dot(pC)) - aCA = acos(pC.dot(pA)) - sumA = aAB + aBC + aCA - - # If the point is outside the triangle: - if (sumA > (pi + error) and sumA < (pi - error)): - int_co = None - - # This is the default case where we either have a planar quad or an n-gon - else: - int_co = intersect_line_plane(edge.verts[0].co, edge.verts[1].co, - face.verts[0].co, face.normal) - return int_co - - -# project_point_plane -# Projects a point onto a plane. Returns a tuple of the projection vector -# and the projected coordinate - -def project_point_plane(pt, plane_co, plane_no): - if ENABLE_DEBUG: - print("project_point_plane was called") - proj_co = intersect_line_plane(pt, pt + plane_no, plane_co, plane_no) - proj_ve = proj_co - pt - if ENABLE_DEBUG: - print("project_point_plane: proj_co is {}\nproj_ve is {}".format(proj_co, proj_ve)) - return (proj_ve, proj_co) - - -# ------------ CHAMPHER HELPER METHODS ------------- - -def is_planar_edge(edge, error=0.000002): - angle = edge.calc_face_angle() - return ((angle < error and angle > -error) or - (angle < (180 + error) and angle > (180 - error))) - - -# ------------- EDGE TOOL METHODS ------------------- - -# Extends an "edge" in two directions: -# - Requires two vertices to be selected. They do not have to form an edge -# - Extends "length" in both directions - -class Extend(Operator): - bl_idname = "mesh.edgetools_extend" - bl_label = "Extend" - bl_description = "Extend the selected edges of vertex pairs" - bl_options = {'REGISTER', 'UNDO'} - - di1: BoolProperty( - name="Forwards", - description="Extend the edge forwards", - default=True - ) - di2: BoolProperty( - name="Backwards", - description="Extend the edge backwards", - default=False - ) - length: FloatProperty( - name="Length", - description="Length to extend the edge", - min=0.0, max=1024.0, - default=1.0 - ) - - def draw(self, context): - layout = self.layout - - row = layout.row(align=True) - row.prop(self, "di1", toggle=True) - row.prop(self, "di2", toggle=True) - - layout.prop(self, "length") - - @classmethod - def poll(cls, context): - ob = context.active_object - return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') - - def invoke(self, context, event): - return self.execute(context) - - def execute(self, context): - try: - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() - - bEdges = bm.edges - bVerts = bm.verts - - edges = [e for e in bEdges if e.select] - verts = [v for v in bVerts if v.select] - - if not is_selected_enough(self, edges, 0, edges_n=1, verts_n=0, types="Edge"): - return {'CANCELLED'} - - if len(edges) > 0: - for e in edges: - vector = e.verts[0].co - e.verts[1].co - vector.length = self.length - - if self.di1: - v = bVerts.new() - if (vector[0] + vector[1] + vector[2]) < 0: - v.co = e.verts[1].co - vector - newE = bEdges.new((e.verts[1], v)) - bEdges.ensure_lookup_table() - else: - v.co = e.verts[0].co + vector - newE = bEdges.new((e.verts[0], v)) - bEdges.ensure_lookup_table() - if self.di2: - v = bVerts.new() - if (vector[0] + vector[1] + vector[2]) < 0: - v.co = e.verts[0].co + vector - newE = bEdges.new((e.verts[0], v)) - bEdges.ensure_lookup_table() - else: - v.co = e.verts[1].co - vector - newE = bEdges.new((e.verts[1], v)) - bEdges.ensure_lookup_table() - else: - vector = verts[0].co - verts[1].co - vector.length = self.length - - if self.di1: - v = bVerts.new() - if (vector[0] + vector[1] + vector[2]) < 0: - v.co = verts[1].co - vector - e = bEdges.new((verts[1], v)) - bEdges.ensure_lookup_table() - else: - v.co = verts[0].co + vector - e = bEdges.new((verts[0], v)) - bEdges.ensure_lookup_table() - if self.di2: - v = bVerts.new() - if (vector[0] + vector[1] + vector[2]) < 0: - v.co = verts[0].co + vector - e = bEdges.new((verts[0], v)) - bEdges.ensure_lookup_table() - else: - v.co = verts[1].co - vector - e = bEdges.new((verts[1], v)) - bEdges.ensure_lookup_table() - - bmesh.update_edit_mesh(me) - - except Exception as e: - error_handlers(self, "mesh.edgetools_extend", e, - reports="Extend Operator failed", func=False) - return {'CANCELLED'} - - return {'FINISHED'} - - -# Creates a series of edges between two edges using spline interpolation. -# This basically just exposes existing functionality in addition to some -# other common methods: Hermite (c-spline), Bezier, and b-spline. These -# alternates I coded myself after some extensive research into spline theory -# -# @todo Figure out what's wrong with the Blender bezier interpolation - -class Spline(Operator): - bl_idname = "mesh.edgetools_spline" - bl_label = "Spline" - bl_description = "Create a spline interplopation between two edges" - bl_options = {'REGISTER', 'UNDO'} - - alg: EnumProperty( - name="Spline Algorithm", - items=[('Blender', "Blender", "Interpolation provided through mathutils.geometry"), - ('Hermite', "C-Spline", "C-spline interpolation"), - ('Bezier', "Bezier", "Bezier interpolation"), - ('B-Spline', "B-Spline", "B-Spline interpolation")], - default='Bezier' - ) - segments: IntProperty( - name="Segments", - description="Number of segments to use in the interpolation", - min=2, max=4096, - soft_max=1024, - default=32 - ) - flip1: BoolProperty( - name="Flip Edge", - description="Flip the direction of the spline on Edge 1", - default=False - ) - flip2: BoolProperty( - name="Flip Edge", - description="Flip the direction of the spline on Edge 2", - default=False - ) - ten1: FloatProperty( - name="Tension", - description="Tension on Edge 1", - min=-4096.0, max=4096.0, - soft_min=-8.0, soft_max=8.0, - default=1.0 - ) - ten2: FloatProperty( - name="Tension", - description="Tension on Edge 2", - min=-4096.0, max=4096.0, - soft_min=-8.0, soft_max=8.0, - default=1.0 - ) - - def draw(self, context): - layout = self.layout - - layout.prop(self, "alg") - layout.prop(self, "segments") - - layout.label(text="Edge 1:") - split = layout.split(factor=0.8, align=True) - split.prop(self, "ten1") - split.prop(self, "flip1", text="", icon="ALIGN", toggle=True) - - layout.label(text="Edge 2:") - split = layout.split(factor=0.8, align=True) - split.prop(self, "ten2") - split.prop(self, "flip2", text="", icon="ALIGN", toggle=True) - - @classmethod - def poll(cls, context): - ob = context.active_object - return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') - - def invoke(self, context, event): - return self.execute(context) - - def execute(self, context): - try: - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() - - bEdges = bm.edges - bVerts = bm.verts - - seg = self.segments - edges = [e for e in bEdges if e.select] - - if not is_selected_enough(self, edges, 0, edges_n=2, verts_n=0, types="Edge"): - return {'CANCELLED'} - - verts = [edges[v // 2].verts[v % 2] for v in range(4)] - - if self.flip1: - v1 = verts[1] - p1_co = verts[1].co - p1_dir = verts[1].co - verts[0].co - else: - v1 = verts[0] - p1_co = verts[0].co - p1_dir = verts[0].co - verts[1].co - if self.ten1 < 0: - p1_dir = -1 * p1_dir - p1_dir.length = -self.ten1 - else: - p1_dir.length = self.ten1 - - if self.flip2: - v2 = verts[3] - p2_co = verts[3].co - p2_dir = verts[2].co - verts[3].co - else: - v2 = verts[2] - p2_co = verts[2].co - p2_dir = verts[3].co - verts[2].co - if self.ten2 < 0: - p2_dir = -1 * p2_dir - p2_dir.length = -self.ten2 - else: - p2_dir.length = self.ten2 - - # Get the interploted coordinates: - if self.alg == 'Blender': - pieces = interpolate_bezier( - p1_co, p1_dir, p2_dir, p2_co, self.segments - ) - elif self.alg == 'Hermite': - pieces = interpolate_line_line( - p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'HERMITE' - ) - elif self.alg == 'Bezier': - pieces = interpolate_line_line( - p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'BEZIER' - ) - elif self.alg == 'B-Spline': - pieces = interpolate_line_line( - p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'BSPLINE' - ) - - verts = [] - verts.append(v1) - # Add vertices and set the points: - for i in range(seg - 1): - v = bVerts.new() - v.co = pieces[i] - bVerts.ensure_lookup_table() - verts.append(v) - verts.append(v2) - # Connect vertices: - for i in range(seg): - e = bEdges.new((verts[i], verts[i + 1])) - bEdges.ensure_lookup_table() - - bmesh.update_edit_mesh(me) - - except Exception as e: - error_handlers(self, "mesh.edgetools_spline", e, - reports="Spline Operator failed", func=False) - return {'CANCELLED'} - - return {'FINISHED'} - - -# Creates edges normal to planes defined between each of two edges and the -# normal or the plane defined by those two edges. -# - Select two edges. The must form a plane. -# - On running the script, eight edges will be created. Delete the -# extras that you don't need. -# - The length of those edges is defined by the variable "length" -# -# @todo Change method from a cross product to a rotation matrix to make the -# angle part work. -# --- todo completed 2/4/2012, but still needs work --- -# @todo Figure out a way to make +/- predictable -# - Maybe use angle between edges and vector direction definition? -# --- TODO COMPLETED ON 2/9/2012 --- - -class Ortho(Operator): - bl_idname = "mesh.edgetools_ortho" - bl_label = "Angle Off Edge" - bl_description = "Creates new edges within an angle from vertices of selected edges" - bl_options = {'REGISTER', 'UNDO'} - - vert1: BoolProperty( - name="Vertice 1", - description="Enable edge creation for Vertice 1", - default=True - ) - vert2: BoolProperty( - name="Vertice 2", - description="Enable edge creation for Vertice 2", - default=True - ) - vert3: BoolProperty( - name="Vertice 3", - description="Enable edge creation for Vertice 3", - default=True - ) - vert4: BoolProperty( - name="Vertice 4", - description="Enable edge creation for Vertice 4", - default=True - ) - pos: BoolProperty( - name="Positive", - description="Enable creation of positive direction edges", - default=True - ) - neg: BoolProperty( - name="Negative", - description="Enable creation of negative direction edges", - default=True - ) - angle: FloatProperty( - name="Angle", - description="Define the angle off of the originating edge", - min=0.0, max=180.0, - default=90.0 - ) - length: FloatProperty( - name="Length", - description="Length of created edges", - min=0.0, max=1024.0, - default=1.0 - ) - # For when only one edge is selected (Possible feature to be testd): - plane: EnumProperty( - name="Plane", - items=[("XY", "X-Y Plane", "Use the X-Y plane as the plane of creation"), - ("XZ", "X-Z Plane", "Use the X-Z plane as the plane of creation"), - ("YZ", "Y-Z Plane", "Use the Y-Z plane as the plane of creation")], - default="XY" - ) - - def draw(self, context): - layout = self.layout - - layout.label(text="Creation:") - split = layout.split() - col = split.column() - - col.prop(self, "vert1", toggle=True) - col.prop(self, "vert2", toggle=True) - - col = split.column() - col.prop(self, "vert3", toggle=True) - col.prop(self, "vert4", toggle=True) - - layout.label(text="Direction:") - row = layout.row(align=False) - row.alignment = 'EXPAND' - row.prop(self, "pos") - row.prop(self, "neg") - - layout.separator() - - col = layout.column(align=True) - col.prop(self, "angle") - col.prop(self, "length") - - @classmethod - def poll(cls, context): - ob = context.active_object - return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') - - def invoke(self, context, event): - return self.execute(context) - - def execute(self, context): - try: - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() - - bVerts = bm.verts - bEdges = bm.edges - edges = [e for e in bEdges if e.select] - vectors = [] - - if not is_selected_enough(self, edges, 0, edges_n=2, verts_n=0, types="Edge"): - return {'CANCELLED'} - - verts = [edges[0].verts[0], - edges[0].verts[1], - edges[1].verts[0], - edges[1].verts[1]] - - cos = intersect_line_line(verts[0].co, verts[1].co, verts[2].co, verts[3].co) - - # If the two edges are parallel: - if cos is None: - self.report({'WARNING'}, - "Selected lines are parallel: results may be unpredictable") - vectors.append(verts[0].co - verts[1].co) - vectors.append(verts[0].co - verts[2].co) - vectors.append(vectors[0].cross(vectors[1])) - vectors.append(vectors[2].cross(vectors[0])) - vectors.append(-vectors[3]) - else: - # Warn the user if they have not chosen two planar edges: - if not is_same_co(cos[0], cos[1]): - self.report({'WARNING'}, - "Selected lines are not planar: results may be unpredictable") - - # This makes the +/- behavior predictable: - if (verts[0].co - cos[0]).length < (verts[1].co - cos[0]).length: - verts[0], verts[1] = verts[1], verts[0] - if (verts[2].co - cos[0]).length < (verts[3].co - cos[0]).length: - verts[2], verts[3] = verts[3], verts[2] - - vectors.append(verts[0].co - verts[1].co) - vectors.append(verts[2].co - verts[3].co) - - # Normal of the plane formed by vector1 and vector2: - vectors.append(vectors[0].cross(vectors[1])) - - # Possible directions: - vectors.append(vectors[2].cross(vectors[0])) - vectors.append(vectors[1].cross(vectors[2])) - - # Set the length: - vectors[3].length = self.length - vectors[4].length = self.length - - # Perform any additional rotations: - matrix = Matrix.Rotation(radians(90 + self.angle), 3, vectors[2]) - vectors.append(matrix * -vectors[3]) # vectors[5] - matrix = Matrix.Rotation(radians(90 - self.angle), 3, vectors[2]) - vectors.append(matrix * vectors[4]) # vectors[6] - vectors.append(matrix * vectors[3]) # vectors[7] - matrix = Matrix.Rotation(radians(90 + self.angle), 3, vectors[2]) - vectors.append(matrix * -vectors[4]) # vectors[8] - - # Perform extrusions and displacements: - # There will be a total of 8 extrusions. One for each vert of each edge. - # It looks like an extrusion will add the new vert to the end of the verts - # list and leave the rest in the same location. - # -- EDIT -- - # It looks like I might be able to do this within "bpy.data" with the ".add" function - - for v in range(len(verts)): - vert = verts[v] - if ((v == 0 and self.vert1) or (v == 1 and self.vert2) or - (v == 2 and self.vert3) or (v == 3 and self.vert4)): - - if self.pos: - new = bVerts.new() - new.co = vert.co - vectors[5 + (v // 2) + ((v % 2) * 2)] - bVerts.ensure_lookup_table() - bEdges.new((vert, new)) - bEdges.ensure_lookup_table() - if self.neg: - new = bVerts.new() - new.co = vert.co + vectors[5 + (v // 2) + ((v % 2) * 2)] - bVerts.ensure_lookup_table() - bEdges.new((vert, new)) - bEdges.ensure_lookup_table() - - bmesh.update_edit_mesh(me) - except Exception as e: - error_handlers(self, "mesh.edgetools_ortho", e, - reports="Angle Off Edge Operator failed", func=False) - return {'CANCELLED'} - - return {'FINISHED'} - - -# Usage: -# Select an edge and a point or an edge and specify the radius (default is 1 BU) -# You can select two edges but it might be unpredictable which edge it revolves -# around so you might have to play with the switch - -class Shaft(Operator): - bl_idname = "mesh.edgetools_shaft" - bl_label = "Shaft" - bl_description = "Create a shaft mesh around an axis" - bl_options = {'REGISTER', 'UNDO'} - - # Selection defaults: - shaftType = 0 - - # For tracking if the user has changed selection: - last_edge: IntProperty( - name="Last Edge", - description="Tracks if user has changed selected edges", - min=0, max=1, - default=0 - ) - last_flip = False - - edge: IntProperty( - name="Edge", - description="Edge to shaft around", - min=0, max=1, - default=0 - ) - flip: BoolProperty( - name="Flip Second Edge", - description="Flip the perceived direction of the second edge", - default=False - ) - radius: FloatProperty( - name="Radius", - description="Shaft Radius", - min=0.0, max=1024.0, - default=1.0 - ) - start: FloatProperty( - name="Starting Angle", - description="Angle to start the shaft at", - min=-360.0, max=360.0, - default=0.0 - ) - finish: FloatProperty( - name="Ending Angle", - description="Angle to end the shaft at", - min=-360.0, max=360.0, - default=360.0 - ) - segments: IntProperty( - name="Shaft Segments", - description="Number of segments to use in the shaft", - min=1, max=4096, - soft_max=512, - default=32 - ) - - def draw(self, context): - layout = self.layout - - if self.shaftType == 0: - layout.prop(self, "edge") - layout.prop(self, "flip") - elif self.shaftType == 3: - layout.prop(self, "radius") - - layout.prop(self, "segments") - layout.prop(self, "start") - layout.prop(self, "finish") - - @classmethod - def poll(cls, context): - ob = context.active_object - return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') - - def invoke(self, context, event): - # Make sure these get reset each time we run: - self.last_edge = 0 - self.edge = 0 - - return self.execute(context) - - def execute(self, context): - try: - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() - - bFaces = bm.faces - bEdges = bm.edges - bVerts = bm.verts - - active = None - edges, verts = [], [] - - # Pre-caclulated values: - rotRange = [radians(self.start), radians(self.finish)] - rads = radians((self.finish - self.start) / self.segments) - - numV = self.segments + 1 - numE = self.segments - - edges = [e for e in bEdges if e.select] - - # Robustness check: there should at least be one edge selected - if not is_selected_enough(self, edges, 0, edges_n=1, verts_n=0, types="Edge"): - return {'CANCELLED'} - - # If two edges are selected: - if len(edges) == 2: - # default: - edge = [0, 1] - vert = [0, 1] - - # By default, we want to shaft around the last selected edge (it - # will be the active edge). We know we are using the default if - # the user has not changed which edge is being shafted around (as - # is tracked by self.last_edge). When they are not the same, then - # the user has changed selection. - # We then need to make sure that the active object really is an edge - # (robustness check) - # Finally, if the active edge is not the initial one, we flip them - # and have the GUI reflect that - if self.last_edge == self.edge: - if isinstance(bm.select_history.active, bmesh.types.BMEdge): - if bm.select_history.active != edges[edge[0]]: - self.last_edge, self.edge = edge[1], edge[1] - edge = [edge[1], edge[0]] - else: - flip_edit_mode() - self.report({'WARNING'}, - "Active geometry is not an edge. Operation Cancelled") - return {'CANCELLED'} - elif self.edge == 1: - edge = [1, 0] - - verts.append(edges[edge[0]].verts[0]) - verts.append(edges[edge[0]].verts[1]) - - if self.flip: - verts = [1, 0] - - verts.append(edges[edge[1]].verts[vert[0]]) - verts.append(edges[edge[1]].verts[vert[1]]) - - self.shaftType = 0 - # If there is more than one edge selected: - # There are some issues with it ATM, so don't expose is it to normal users - # @todo Fix edge connection ordering issue - elif ENABLE_DEBUG and len(edges) > 2: - if isinstance(bm.select_history.active, bmesh.types.BMEdge): - active = bm.select_history.active - edges.remove(active) - # Get all the verts: - # edges = order_joined_edges(edges[0]) - verts = [] - for e in edges: - if verts.count(e.verts[0]) == 0: - verts.append(e.verts[0]) - if verts.count(e.verts[1]) == 0: - verts.append(e.verts[1]) - else: - flip_edit_mode() - self.report({'WARNING'}, - "Active geometry is not an edge. Operation Cancelled") - return {'CANCELLED'} - self.shaftType = 1 - else: - verts.append(edges[0].verts[0]) - verts.append(edges[0].verts[1]) - - for v in bVerts: - if v.select and verts.count(v) == 0: - verts.append(v) - v.select = False - if len(verts) == 2: - self.shaftType = 3 - else: - self.shaftType = 2 - - # The vector denoting the axis of rotation: - if self.shaftType == 1: - axis = active.verts[1].co - active.verts[0].co - else: - axis = verts[1].co - verts[0].co - - # We will need a series of rotation matrices. We could use one which - # would be faster but also might cause propagation of error - # matrices = [] - # for i in range(numV): - # matrices.append(Matrix.Rotation((rads * i) + rotRange[0], 3, axis)) - matrices = [Matrix.Rotation((rads * i) + rotRange[0], 3, axis) for i in range(numV)] - - # New vertice coordinates: - verts_out = [] - - # If two edges were selected: - # - If the lines are not parallel, then it will create a cone-like shaft - if self.shaftType == 0: - for i in range(len(verts) - 2): - init_vec = distance_point_line(verts[i + 2].co, verts[0].co, verts[1].co) - co = init_vec + verts[i + 2].co - # These will be rotated about the origin so will need to be shifted: - for j in range(numV): - verts_out.append(co - (matrices[j] * init_vec)) - elif self.shaftType == 1: - for i in verts: - init_vec = distance_point_line(i.co, active.verts[0].co, active.verts[1].co) - co = init_vec + i.co - # These will be rotated about the origin so will need to be shifted: - for j in range(numV): - verts_out.append(co - (matrices[j] * init_vec)) - # Else if a line and a point was selected: - elif self.shaftType == 2: - init_vec = distance_point_line(verts[2].co, verts[0].co, verts[1].co) - # These will be rotated about the origin so will need to be shifted: - verts_out = [ - (verts[i].co - (matrices[j] * init_vec)) for i in range(2) for j in range(numV) - ] - else: - # Else the above are not possible, so we will just use the edge: - # - The vector defined by the edge is the normal of the plane for the shaft - # - The shaft will have radius "radius" - if is_axial(verts[0].co, verts[1].co) is None: - proj = (verts[1].co - verts[0].co) - proj[2] = 0 - norm = proj.cross(verts[1].co - verts[0].co) - vec = norm.cross(verts[1].co - verts[0].co) - vec.length = self.radius - elif is_axial(verts[0].co, verts[1].co) == 'Z': - vec = verts[0].co + Vector((0, 0, self.radius)) - else: - vec = verts[0].co + Vector((0, self.radius, 0)) - init_vec = distance_point_line(vec, verts[0].co, verts[1].co) - # These will be rotated about the origin so will need to be shifted: - verts_out = [ - (verts[i].co - (matrices[j] * init_vec)) for i in range(2) for j in range(numV) - ] - - # We should have the coordinates for a bunch of new verts - # Now add the verts and build the edges and then the faces - - newVerts = [] - - if self.shaftType == 1: - # Vertices: - for i in range(numV * len(verts)): - new = bVerts.new() - new.co = verts_out[i] - bVerts.ensure_lookup_table() - new.select = True - newVerts.append(new) - # Edges: - for i in range(numE): - for j in range(len(verts)): - e = bEdges.new((newVerts[i + (numV * j)], newVerts[i + (numV * j) + 1])) - bEdges.ensure_lookup_table() - e.select = True - for i in range(numV): - for j in range(len(verts) - 1): - e = bEdges.new((newVerts[i + (numV * j)], newVerts[i + (numV * (j + 1))])) - bEdges.ensure_lookup_table() - e.select = True - - # Faces: There is a problem with this right now - """ - for i in range(len(edges)): - for j in range(numE): - f = bFaces.new((newVerts[i], newVerts[i + 1], - newVerts[i + (numV * j) + 1], newVerts[i + (numV * j)])) - f.normal_update() - """ - else: - # Vertices: - for i in range(numV * 2): - new = bVerts.new() - new.co = verts_out[i] - new.select = True - bVerts.ensure_lookup_table() - newVerts.append(new) - # Edges: - for i in range(numE): - e = bEdges.new((newVerts[i], newVerts[i + 1])) - e.select = True - bEdges.ensure_lookup_table() - e = bEdges.new((newVerts[i + numV], newVerts[i + numV + 1])) - e.select = True - bEdges.ensure_lookup_table() - for i in range(numV): - e = bEdges.new((newVerts[i], newVerts[i + numV])) - e.select = True - bEdges.ensure_lookup_table() - # Faces: - for i in range(numE): - f = bFaces.new((newVerts[i], newVerts[i + 1], - newVerts[i + numV + 1], newVerts[i + numV])) - bFaces.ensure_lookup_table() - f.normal_update() - - bmesh.update_edit_mesh(me) - - except Exception as e: - error_handlers(self, "mesh.edgetools_shaft", e, - reports="Shaft Operator failed", func=False) - return {'CANCELLED'} - - return {'FINISHED'} - - -# "Slices" edges crossing a plane defined by a face - -class Slice(Operator): - bl_idname = "mesh.edgetools_slice" - bl_label = "Slice" - bl_description = "Cut edges at the plane defined by a selected face" - bl_options = {'REGISTER', 'UNDO'} - - make_copy: BoolProperty( - name="Make Copy", - description="Make new vertices at intersection points instead of splitting the edge", - default=False - ) - rip: BoolProperty( - name="Rip", - description="Split into two edges that DO NOT share an intersection vertex", - default=True - ) - pos: BoolProperty( - name="Positive", - description="Remove the portion on the side of the face normal", - default=False - ) - neg: BoolProperty( - name="Negative", - description="Remove the portion on the side opposite of the face normal", - default=False - ) - - def draw(self, context): - layout = self.layout - - layout.prop(self, "make_copy") - if not self.make_copy: - layout.prop(self, "rip") - layout.label(text="Remove Side:") - layout.prop(self, "pos") - layout.prop(self, "neg") - - @classmethod - def poll(cls, context): - ob = context.active_object - return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') - - def invoke(self, context, event): - return self.execute(context) - - def execute(self, context): - try: - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() - - bVerts = bm.verts - bEdges = bm.edges - bFaces = bm.faces - - face, normal = None, None - - # Find the selected face. This will provide the plane to project onto: - # - First check to use the active face. Allows users to just - # select a bunch of faces with the last being the cutting plane - # - If that fails, then use the first found selected face in the BMesh face list - if isinstance(bm.select_history.active, bmesh.types.BMFace): - face = bm.select_history.active - normal = bm.select_history.active.normal - bm.select_history.active.select = False - else: - for f in bFaces: - if f.select: - face = f - normal = f.normal - f.select = False - break - - # If we don't find a selected face exit: - if face is None: - flip_edit_mode() - self.report({'WARNING'}, - "Please select a face as the cutting plane. Operation Cancelled") - return {'CANCELLED'} - - # Warn the user if they are using an n-gon might lead to some odd results - elif len(face.verts) > 4 and not is_face_planar(face): - self.report({'WARNING'}, - "Selected face is an N-gon. Results may be unpredictable") - - if ENABLE_DEBUG: - dbg = 0 - print("Number of Edges: ", len(bEdges)) - - for e in bEdges: - if ENABLE_DEBUG: - print("Looping through Edges - ", dbg) - dbg = dbg + 1 - - # Get the end verts on the edge: - v1 = e.verts[0] - v2 = e.verts[1] - - # Make sure that verts are not a part of the cutting plane: - if e.select and (v1 not in face.verts and v2 not in face.verts): - if len(face.verts) < 5: # Not an n-gon - intersection = intersect_line_face(e, face, True) - else: - intersection = intersect_line_plane(v1.co, v2.co, face.verts[0].co, normal) - - if ENABLE_DEBUG: - print("Intersection: ", intersection) - - # If an intersection exists find the distance of each of the end - # points from the plane, with "positive" being in the direction - # of the cutting plane's normal. If the points are on opposite - # side of the plane, then it intersects and we need to cut it - if intersection is not None: - bVerts.ensure_lookup_table() - bEdges.ensure_lookup_table() - bFaces.ensure_lookup_table() - - d1 = distance_point_to_plane(v1.co, face.verts[0].co, normal) - d2 = distance_point_to_plane(v2.co, face.verts[0].co, normal) - # If they have different signs, then the edge crosses the cutting plane: - if abs(d1 + d2) < abs(d1 - d2): - # Make the first vertex the positive one: - if d1 < d2: - v2, v1 = v1, v2 - - if self.make_copy: - new = bVerts.new() - new.co = intersection - new.select = True - bVerts.ensure_lookup_table() - elif self.rip: - if ENABLE_DEBUG: - print("Branch rip engaged") - newV1 = bVerts.new() - newV1.co = intersection - bVerts.ensure_lookup_table() - if ENABLE_DEBUG: - print("newV1 created", end='; ') - - newV2 = bVerts.new() - newV2.co = intersection - bVerts.ensure_lookup_table() - - if ENABLE_DEBUG: - print("newV2 created", end='; ') - - newE1 = bEdges.new((v1, newV1)) - newE2 = bEdges.new((v2, newV2)) - bEdges.ensure_lookup_table() - - if ENABLE_DEBUG: - print("new edges created", end='; ') - - if e.is_valid: - bEdges.remove(e) - - bEdges.ensure_lookup_table() - - if ENABLE_DEBUG: - print("Old edge removed.\nWe're done with this edge") - else: - new = list(bmesh.utils.edge_split(e, v1, 0.5)) - bEdges.ensure_lookup_table() - new[1].co = intersection - e.select = False - new[0].select = False - if self.pos: - bEdges.remove(new[0]) - if self.neg: - bEdges.remove(e) - bEdges.ensure_lookup_table() - - if ENABLE_DEBUG: - print("The Edge Loop has exited. Now to update the bmesh") - dbg = 0 - - bmesh.update_edit_mesh(me) - - except Exception as e: - error_handlers(self, "mesh.edgetools_slice", e, - reports="Slice Operator failed", func=False) - return {'CANCELLED'} - - return {'FINISHED'} - - -# This projects the selected edges onto the selected plane -# and/or both points on the selected edge - -class Project(Operator): - bl_idname = "mesh.edgetools_project" - bl_label = "Project" - bl_description = ("Projects the selected Vertices/Edges onto a selected plane\n" - "(Active is projected onto the rest)") - bl_options = {'REGISTER', 'UNDO'} - - make_copy: BoolProperty( - name="Make Copy", - description="Make duplicates of the vertices instead of altering them", - default=False - ) - - def draw(self, context): - layout = self.layout - layout.prop(self, "make_copy") - - @classmethod - def poll(cls, context): - ob = context.active_object - return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') - - def invoke(self, context, event): - return self.execute(context) - - def execute(self, context): - try: - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() - - bFaces = bm.faces - bVerts = bm.verts - - fVerts = [] - - # Find the selected face. This will provide the plane to project onto: - # @todo Check first for an active face - for f in bFaces: - if f.select: - for v in f.verts: - fVerts.append(v) - normal = f.normal - f.select = False - break - - for v in bVerts: - if v.select: - if v in fVerts: - v.select = False - continue - d = distance_point_to_plane(v.co, fVerts[0].co, normal) - if self.make_copy: - temp = v - v = bVerts.new() - v.co = temp.co - bVerts.ensure_lookup_table() - vector = normal - vector.length = abs(d) - v.co = v.co - (vector * sign(d)) - v.select = False - - bmesh.update_edit_mesh(me) - - except Exception as e: - error_handlers(self, "mesh.edgetools_project", e, - reports="Project Operator failed", func=False) - - return {'CANCELLED'} - - return {'FINISHED'} - - -# Project_End is for projecting/extending an edge to meet a plane -# This is used be selecting a face to define the plane then all the edges -# Then move the vertices in the edge that is closest to the -# plane to the coordinates of the intersection of the edge and the plane - -class Project_End(Operator): - bl_idname = "mesh.edgetools_project_end" - bl_label = "Project (End Point)" - bl_description = ("Projects the vertices of the selected\n" - "edges closest to a plane onto that plane") - bl_options = {'REGISTER', 'UNDO'} - - make_copy: BoolProperty( - name="Make Copy", - description="Make a duplicate of the vertice instead of moving it", - default=False - ) - keep_length: BoolProperty( - name="Keep Edge Length", - description="Maintain edge lengths", - default=False - ) - use_force: BoolProperty( - name="Use opposite vertices", - description="Force the usage of the vertices at the other end of the edge", - default=False - ) - use_normal: BoolProperty( - name="Project along normal", - description="Use the plane's normal as the projection direction", - default=False - ) - - def draw(self, context): - layout = self.layout - - if not self.keep_length: - layout.prop(self, "use_normal") - layout.prop(self, "make_copy") - layout.prop(self, "use_force") - - @classmethod - def poll(cls, context): - ob = context.active_object - return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') - - def invoke(self, context, event): - return self.execute(context) - - def execute(self, context): - try: - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() - - bFaces = bm.faces - bEdges = bm.edges - bVerts = bm.verts - - fVerts = [] - - # Find the selected face. This will provide the plane to project onto: - for f in bFaces: - if f.select: - for v in f.verts: - fVerts.append(v) - normal = f.normal - f.select = False - break - - for e in bEdges: - if e.select: - v1 = e.verts[0] - v2 = e.verts[1] - if v1 in fVerts or v2 in fVerts: - e.select = False - continue - intersection = intersect_line_plane(v1.co, v2.co, fVerts[0].co, normal) - if intersection is not None: - # Use abs because we don't care what side of plane we're on: - d1 = distance_point_to_plane(v1.co, fVerts[0].co, normal) - d2 = distance_point_to_plane(v2.co, fVerts[0].co, normal) - # If d1 is closer than we use v1 as our vertice: - # "xor" with 'use_force': - if (abs(d1) < abs(d2)) is not self.use_force: - if self.make_copy: - v1 = bVerts.new() - v1.co = e.verts[0].co - bVerts.ensure_lookup_table() - bEdges.ensure_lookup_table() - if self.keep_length: - v1.co = intersection - elif self.use_normal: - vector = normal - vector.length = abs(d1) - v1.co = v1.co - (vector * sign(d1)) - else: - v1.co = intersection - else: - if self.make_copy: - v2 = bVerts.new() - v2.co = e.verts[1].co - bVerts.ensure_lookup_table() - bEdges.ensure_lookup_table() - if self.keep_length: - v2.co = intersection - elif self.use_normal: - vector = normal - vector.length = abs(d2) - v2.co = v2.co - (vector * sign(d2)) - else: - v2.co = intersection - e.select = False - - bmesh.update_edit_mesh(me) - - except Exception as e: - error_handlers(self, "mesh.edgetools_project_end", e, - reports="Project (End Point) Operator failed", func=False) - return {'CANCELLED'} - - return {'FINISHED'} - - -class VIEW3D_MT_edit_mesh_edgetools(Menu): - bl_label = "Edge Tools" - bl_description = "Various tools for manipulating edges" - - def draw(self, context): - layout = self.layout - - layout.operator("mesh.edgetools_extend") - layout.operator("mesh.edgetools_spline") - layout.operator("mesh.edgetools_ortho") - layout.operator("mesh.edgetools_shaft") - layout.operator("mesh.edgetools_slice") - layout.separator() - - layout.operator("mesh.edgetools_project") - layout.operator("mesh.edgetools_project_end") - - -# define classes for registration -classes = ( - VIEW3D_MT_edit_mesh_edgetools, - Extend, - Spline, - Ortho, - Shaft, - Slice, - Project, - Project_End, - ) - - -# registering and menu integration -def register(): - for cls in classes: - bpy.utils.register_class(cls) - - -# unregistering and removing menus -def unregister(): - for cls in classes: - bpy.utils.unregister_class(cls) - - -if __name__ == "__main__": - register() diff --git a/mesh_extra_tools/mesh_extrude_and_reshape.py b/mesh_extra_tools/mesh_extrude_and_reshape.py deleted file mode 100644 index ec7b969d7..000000000 --- a/mesh_extra_tools/mesh_extrude_and_reshape.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_fastloop.py b/mesh_extra_tools/mesh_fastloop.py deleted file mode 100644 index b52a35077..000000000 --- a/mesh_extra_tools/mesh_fastloop.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_filletplus.py b/mesh_extra_tools/mesh_filletplus.py deleted file mode 100644 index a54eba7b2..000000000 --- a/mesh_extra_tools/mesh_filletplus.py +++ /dev/null @@ -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'} diff --git a/mesh_extra_tools/mesh_help.py b/mesh_extra_tools/mesh_help.py deleted file mode 100644 index 30047a8b6..000000000 --- a/mesh_extra_tools/mesh_help.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_mextrude_plus.py b/mesh_extra_tools/mesh_mextrude_plus.py deleted file mode 100644 index 5fa2aa2b7..000000000 --- a/mesh_extra_tools/mesh_mextrude_plus.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_offset_edges.py b/mesh_extra_tools/mesh_offset_edges.py deleted file mode 100644 index b6d760b16..000000000 --- a/mesh_extra_tools/mesh_offset_edges.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_pen_tool.py b/mesh_extra_tools/mesh_pen_tool.py deleted file mode 100644 index 76d5e0aa6..000000000 --- a/mesh_extra_tools/mesh_pen_tool.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_select_tools/__init__.py b/mesh_extra_tools/mesh_select_tools/__init__.py deleted file mode 100644 index 74d42f1cf..000000000 --- a/mesh_extra_tools/mesh_select_tools/__init__.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_select_tools/mesh_index_select.py b/mesh_extra_tools/mesh_select_tools/mesh_index_select.py deleted file mode 100644 index a9e14dfe2..000000000 --- a/mesh_extra_tools/mesh_select_tools/mesh_index_select.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_select_tools/mesh_info_select.py b/mesh_extra_tools/mesh_select_tools/mesh_info_select.py deleted file mode 100644 index 2a9f1991b..000000000 --- a/mesh_extra_tools/mesh_select_tools/mesh_info_select.py +++ /dev/null @@ -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" diff --git a/mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py b/mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py deleted file mode 100644 index cf25cf6fc..000000000 --- a/mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py +++ /dev/null @@ -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'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_select_by_edge_length.py b/mesh_extra_tools/mesh_select_tools/mesh_select_by_edge_length.py deleted file mode 100644 index cea9976f2..000000000 --- a/mesh_extra_tools/mesh_select_tools/mesh_select_by_edge_length.py +++ /dev/null @@ -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'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py b/mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py deleted file mode 100644 index cecf564c8..000000000 --- a/mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py +++ /dev/null @@ -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'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py b/mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py deleted file mode 100644 index 7afe80fe2..000000000 --- a/mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py +++ /dev/null @@ -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'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py b/mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py deleted file mode 100644 index 2c574f2df..000000000 --- a/mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py +++ /dev/null @@ -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'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py b/mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py deleted file mode 100644 index c787b3058..000000000 --- a/mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/mesh_vertex_chamfer.py b/mesh_extra_tools/mesh_vertex_chamfer.py deleted file mode 100644 index d3a4bc7b0..000000000 --- a/mesh_extra_tools/mesh_vertex_chamfer.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/pkhg_faces.py b/mesh_extra_tools/pkhg_faces.py deleted file mode 100644 index 31497aa45..000000000 --- a/mesh_extra_tools/pkhg_faces.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/random_vertices.py b/mesh_extra_tools/random_vertices.py deleted file mode 100644 index 51f8be242..000000000 --- a/mesh_extra_tools/random_vertices.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/split_solidify.py b/mesh_extra_tools/split_solidify.py deleted file mode 100644 index 16a1c3275..000000000 --- a/mesh_extra_tools/split_solidify.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/vertex_align.py b/mesh_extra_tools/vertex_align.py deleted file mode 100644 index eb66d747e..000000000 --- a/mesh_extra_tools/vertex_align.py +++ /dev/null @@ -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() diff --git a/mesh_extra_tools/vfe_specials.py b/mesh_extra_tools/vfe_specials.py deleted file mode 100644 index 714f1b3e1..000000000 --- a/mesh_extra_tools/vfe_specials.py +++ /dev/null @@ -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() -- GitLab