Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2019-05-24 09:03:30 +0300
committermeta-androcto <meta.androcto1@gmail.com>2019-05-24 09:03:30 +0300
commit9e99e90f08c985cb764ecb3a7bba6ff534d4d874 (patch)
tree1e415d32af5aac63399250bf15eeaad97684751d
parent01d80b8f602f392d975255d5fef97dcab2e0589b (diff)
mesh_extra_tools: move to contrib: T63750
-rw-r--r--mesh_extra_tools/__init__.py905
-rw-r--r--mesh_extra_tools/face_inset_fillet.py335
-rw-r--r--mesh_extra_tools/icons/icons.py34
-rw-r--r--mesh_extra_tools/icons/ngon.pngbin6747 -> 0 bytes
-rw-r--r--mesh_extra_tools/icons/triangle.pngbin4153 -> 0 bytes
-rw-r--r--mesh_extra_tools/mesh_check.py370
-rw-r--r--mesh_extra_tools/mesh_cut_faces.py266
-rw-r--r--mesh_extra_tools/mesh_edge_roundifier.py1380
-rw-r--r--mesh_extra_tools/mesh_edges_floor_plan.py384
-rw-r--r--mesh_extra_tools/mesh_edges_length.py341
-rw-r--r--mesh_extra_tools/mesh_edgetools.py1878
-rw-r--r--mesh_extra_tools/mesh_extrude_and_reshape.py378
-rw-r--r--mesh_extra_tools/mesh_fastloop.py112
-rw-r--r--mesh_extra_tools/mesh_filletplus.py412
-rw-r--r--mesh_extra_tools/mesh_help.py244
-rw-r--r--mesh_extra_tools/mesh_mextrude_plus.py370
-rw-r--r--mesh_extra_tools/mesh_offset_edges.py823
-rw-r--r--mesh_extra_tools/mesh_pen_tool.py568
-rw-r--r--mesh_extra_tools/mesh_select_tools/__init__.py70
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_index_select.py168
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_info_select.py111
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py208
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_select_by_edge_length.py234
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py196
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py75
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py134
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py632
-rw-r--r--mesh_extra_tools/mesh_vertex_chamfer.py161
-rw-r--r--mesh_extra_tools/pkhg_faces.py835
-rw-r--r--mesh_extra_tools/random_vertices.py140
-rw-r--r--mesh_extra_tools/split_solidify.py203
-rw-r--r--mesh_extra_tools/vertex_align.py301
-rw-r--r--mesh_extra_tools/vfe_specials.py95
33 files changed, 0 insertions, 12363 deletions
diff --git a/mesh_extra_tools/__init__.py b/mesh_extra_tools/__init__.py
deleted file mode 100644
index 669d22f0..00000000
--- 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 8af709c1..00000000
--- 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 d98c8c06..00000000
--- 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 b1a79b98..00000000
--- a/mesh_extra_tools/icons/ngon.png
+++ /dev/null
Binary files differ
diff --git a/mesh_extra_tools/icons/triangle.png b/mesh_extra_tools/icons/triangle.png
deleted file mode 100644
index 05f3a8db..00000000
--- a/mesh_extra_tools/icons/triangle.png
+++ /dev/null
Binary files differ
diff --git a/mesh_extra_tools/mesh_check.py b/mesh_extra_tools/mesh_check.py
deleted file mode 100644
index 2acfe184..00000000
--- 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 1522b159..00000000
--- 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 f216b1db..00000000
--- 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 1804c79a..00000000
--- 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 d8c3ec5f..00000000
--- 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 0547e915..00000000
--- 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 ec7b969d..00000000
--- 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 b52a3507..00000000
--- 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 a54eba7b..00000000
--- 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 30047a8b..00000000
--- 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 5fa2aa2b..00000000
--- 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 b6d760b1..00000000
--- 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 76d5e0aa..00000000
--- 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 74d42f1c..00000000
--- 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 a9e14dfe..00000000
--- 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 2a9f1991..00000000
--- 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 cf25cf6f..00000000
--- 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 cea9976f..00000000
--- 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 cecf564c..00000000
--- 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 7afe80fe..00000000
--- 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 2c574f2d..00000000
--- 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 c787b305..00000000
--- 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 d3a4bc7b..00000000
--- 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 31497aa4..00000000
--- 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 51f8be24..00000000
--- 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 16a1c327..00000000
--- 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 eb66d747..00000000
--- 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 714f1b3e..00000000
--- 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()