diff options
author | lijenstina <lijenstina@gmail.com> | 2017-05-21 08:26:34 +0300 |
---|---|---|
committer | lijenstina <lijenstina@gmail.com> | 2017-05-21 08:26:34 +0300 |
commit | 48a69db5444591ba88ca9794dbdb2b8d3a2c9984 (patch) | |
tree | 9e33a71a39736a8472bf039f6685c4b49c8b11ff /mesh_extra_tools | |
parent | aaeb073354aa00abb198c52af42dfbafeb7e069a (diff) |
Mesh Extra Tools: Update to version 0.33, various fixes
Bump version to 0.33
Some Pep8 clean up and general fixes
Some UI updates
Replace Mesh to wall with a similar results script
mesh_edges_floor_plan (for reasons see T51483)
It is not 1 to 1 replacement but should be more stable
If someone wants to continue development it's absolutely
possible to be re-included later on
Vertex Align: cleanup and refactor code
introduce a stored alignment setting, proper help operator call
move the property group to init, remove register call
Mesh Check: cleanup
Move the UI element draw code from init
to the Face/Info select, cleanup
mesh_info_select:
Add a timer for redraw (not sure if it is the best solution)
Add settings remove refresh operator (as edit mode is available)
Mesh Edge Tools:
remove the Intersect_Line_Face operator, add general error handling
fix several crashes with non proper selections passed
Select tools - replace deprecated imp call
Diffstat (limited to 'mesh_extra_tools')
32 files changed, 3337 insertions, 2970 deletions
diff --git a/mesh_extra_tools/__init__.py b/mesh_extra_tools/__init__.py index 72b866b4..eeeb7594 100644 --- a/mesh_extra_tools/__init__.py +++ b/mesh_extra_tools/__init__.py @@ -17,20 +17,21 @@ # ##### END GPL LICENSE BLOCK ##### # Contributed to by: -# meta-androcto, Hidesato Ikeya, zmj100, luxuy_BlenderCN, TrumanBlending, PKHG, # +# 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, 2), + "version": (0, 3, 3), "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", - "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" + "Py/Scripts/Modeling/Extra_Tools", "category": "Mesh"} @@ -46,7 +47,7 @@ if "bpy" in locals(): importlib.reload(mesh_edge_roundifier) importlib.reload(mesh_cut_faces) importlib.reload(split_solidify) - importlib.reload(mesh_to_wall) + importlib.reload(mesh_edges_floor_plan) importlib.reload(mesh_edges_length) importlib.reload(random_vertices) importlib.reload(mesh_fastloop) @@ -76,7 +77,7 @@ else: from . import mesh_edge_roundifier from . import mesh_cut_faces from . import split_solidify - from . import mesh_to_wall + from . import mesh_edges_floor_plan from . import mesh_edges_length from . import random_vertices from . import mesh_fastloop @@ -101,8 +102,6 @@ else: import bpy import bpy_extras.keyconfig_utils -import bmesh -from bpy.props import EnumProperty from bpy.types import ( Menu, Panel, @@ -112,6 +111,9 @@ from bpy.types import ( from bpy.props import ( BoolProperty, BoolVectorProperty, + EnumProperty, + FloatProperty, + FloatVectorProperty, IntVectorProperty, PointerProperty, ) @@ -159,7 +161,7 @@ class VIEW3D_MT_edit_mesh_extras(Menu): 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("bpt.mesh_to_wall", text="Edge(s) to Wall") + col.operator("mesh.edges_floor_plan") col = split.column() col.label(text="Utilities", icon="SCRIPTWIN") @@ -241,21 +243,35 @@ class EditToolsPanel(Panel): 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 = context.scene.va_custom_props.en0 # get the properties list - layout = self.layout - layout.label(text="Vertex Align:", icon="VERTEXSEL") - layout.prop(context.scene.va_custom_props, 'en0', expand = False) # Draw the menu with 2 options - if cen0 == 'vertex': - row = layout.split(0.60) - row.label('Store data:') - row.operator('va.op0_store_id', text = 'Vertex') - row1 = layout.split(0.8, align=True) - row1.operator('va.op2_align_id', text = 'Align to Axis') - row1.operator('va.op7_help_id', text = '', icon = "LAYER_USED") - elif cen0 == 'coordinates': - layout.operator('va.op3_coord_list_id', text = 'Align Coordinates') + # 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() @@ -276,9 +292,9 @@ class EditToolsPanel(Panel): row = layout.split(0.8, align=True) row.operator("mesh.fillet_plus", text="Fillet plus") - prop = row.operator("mesh.extra_tools_help", icon="LAYER_USED") - prop.help_ids = "mesh_filletplus" - prop.popup_size = 400 + 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") @@ -296,9 +312,11 @@ class EditToolsPanel(Panel): icon="LAYER_USED").help_ids = "mesh_edges_length" row = layout.split(0.8, align=True) - row.operator("bpt.mesh_to_wall", text="Edge(s) to Wall") - row.operator("mesh.extra_tools_help", - icon="LAYER_USED").help_ids = "mesh_to_wall" + 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() @@ -364,82 +382,73 @@ class EditToolsPanel(Panel): row = layout.split(0.8, align=True) row.operator("object_ot.fastloop", text="Fast Loop") - prop = row.operator("mesh.extra_tools_help", icon="LAYER_USED") - prop.help_ids = "mesh_fastloop" - prop.popup_size = 400 - - row = layout.row() - row.operator("mesh.flip_normals", text="Normals Flip") - - row = layout.row() - row.operator("mesh.remove_doubles", text="Remove Doubles") - - row = layout.row() - row.operator("mesh.subdivide", text="Subdivide") + props = row.operator("mesh.extra_tools_help", icon="LAYER_USED") + props.help_ids = "mesh_fastloop" + props.popup_size = 400 - row = layout.row() - row.operator("mesh.dissolve_limited", text="Dissolve Limited") + 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 - - layout.prop(mesh_check, "mesh_check_use") - + 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() - 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(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: - row = layout.row() - row.prop(mesh_check, "edge_width") - row = layout.row() - row.prop(mesh_check, "custom_tri_color",text="Tris color" ) - row = layout.row() - row.prop(mesh_check, "custom_ngons_color") - row = layout.row() - row.prop(mesh_check, "face_opacity") - if bpy.context.object.mode == 'EDIT': - obj = bpy.context.object - me = obj.data - bm = bmesh.from_edit_mesh(me) + col = layout.column(align=True) + col.prop(mesh_check, "edge_width") + col.prop(mesh_check, "face_opacity") - info_str = "" - tris = ngons = 0 + row = layout.row() + row.label(text="Custom Colors:", icon="COLOR") - for f in bm.faces: + col = layout.column().split(percentage=0.1, align=True) + col.label(text="", icon_value=tris.icon_id) + col.prop(mesh_check, "custom_tri_color", text="") - v = len(f.verts) - if v == 3: - tris += 1 - elif v > 4: - ngons += 1 + col = layout.column().split(percentage=0.1, align=True) + col.label(text="", icon_value=ngons.icon_id) + col.prop(mesh_check, "custom_ngons_color", text="") - bmesh.update_edit_mesh(me) - info_str = " Ngons: %i Tris: %i" % (ngons, tris) + layout.separator() - split = layout.split(percentage=0.1) - split.separator() - split.label(info_str, icon='MESH_DATA') + 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: - split = layout.split(percentage=0.1) - split.separator() - split2 = split.split() - row = split2.row() - row.prop(mesh_check, "finer_lines_behind_use") + row.prop(mesh_check, "finer_lines_behind_use", icon="ORTHO") # ********** Edit Multiselect ********** @@ -735,6 +744,55 @@ class MeshExtraToolsSceneProps(PropertyGroup): 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 @@ -802,7 +860,7 @@ def register(): vfe_specials.register() mesh_extrude_and_reshape.register() mesh_check.register() - vertex_align.register() + bpy.utils.register_module(__name__) # Register Scene Properties @@ -827,7 +885,6 @@ def unregister(): vfe_specials.unregister() mesh_extrude_and_reshape.unregister() mesh_check.unregister() - vertex_align.unregister() del bpy.types.Scene.mesh_extra_tools del bpy.types.Object.tkkey diff --git a/mesh_extra_tools/face_inset_fillet.py b/mesh_extra_tools/face_inset_fillet.py index fc758b7c..2f46115d 100644 --- a/mesh_extra_tools/face_inset_fillet.py +++ b/mesh_extra_tools/face_inset_fillet.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. # -# 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. # -# 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 LICENCE BLOCK ***** +# ##### END GPL LICENSE BLOCK ##### + # based completely on addon by zmj100 # added some distance limits to prevent overlap - max12345 @@ -31,7 +31,10 @@ from bpy.props import ( BoolProperty, EnumProperty, ) -from math import tan, cos, degrees, radians, sin +from math import ( + sin, cos, tan, + degrees, radians, + ) from mathutils import Matrix @@ -97,11 +100,17 @@ def face_inset_fillet(bme, face_index_list, inset_amount, distance, 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)) + 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)) + p6 = angle_rotation( + p, p - val, + ((p - (vec1.normalized() * adj)) - (p - (vec2.normalized() * adj))), + -radians(90) + ) orientation_vertex_list.append(p6) @@ -200,6 +209,7 @@ def face_inset_fillet(bme, face_index_list, inset_amount, distance, bme.faces.index_update() del_ = [bme.faces.remove(f) for f in list_del] + if del_: del del_ @@ -209,59 +219,59 @@ def face_inset_fillet(bme, face_index_list, inset_amount, distance, 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 of\n" + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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" - ) + 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 - ) + 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 @@ -308,8 +318,10 @@ class MESH_OT_face_inset_fillet(Operator): 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") + self.report({'WARNING'}, + "No suitable Face selection found. Operation cancelled") edit_mode_in() + return {'CANCELLED'} elif len(face_index_list) != 0: diff --git a/mesh_extra_tools/icons/icons.py b/mesh_extra_tools/icons/icons.py index 92b7d0db..d98c8c06 100644 --- a/mesh_extra_tools/icons/icons.py +++ b/mesh_extra_tools/icons/icons.py @@ -1,28 +1,31 @@ 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"] - + + 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(): diff --git a/mesh_extra_tools/mesh_check.py b/mesh_extra_tools/mesh_check.py index cc4187ca..c3d42837 100644 --- a/mesh_extra_tools/mesh_check.py +++ b/mesh_extra_tools/mesh_check.py @@ -1,13 +1,14 @@ # 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" - } +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 @@ -180,10 +181,9 @@ def mesh_check_draw_callback(): 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) + ((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] ) @@ -220,24 +220,28 @@ def updateBGLData(self, context): 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) - + 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 @@ -246,11 +250,13 @@ def updateBGLData(self, context): 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( - items=(('tris', 'Tris', ""), - ('ngons', 'Ngons', "")), + name="Face Type", + items=(('tris', "Tris", "Colorize Triangles in the Mesh"), + ('ngons', "Ngons", "Colorize Ngons in the Mesh")), default='ngons' ) @@ -273,61 +279,61 @@ class FaceTypeSelect(Operator): class MeshCheckCollectionGroup(PropertyGroup): mesh_check_use = BoolProperty( - name="Mesh Check", - description="Display Mesh Check options", - default=False, - update=updateBGLData - ) + name="Mesh Check", + description="Display Mesh Check options", + default=False, + update=updateBGLData + ) display_faces = BoolProperty( - name="Display Faces", - description="Use BGL to display ngons en tris of the mesh", - default=False, - update=updateBGLData - ) + name="Display Faces", + description="Use BGL to display Ngons and Tris of the mesh", + default=False, + update=updateBGLData + ) edge_width = FloatProperty( - name="Width", - description="Edges width in pixels", - min=1.0, - max=10.0, - default=3.0, - subtype='PIXEL', - update=updateBGLData - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 @@ -340,6 +346,7 @@ classes = ( def register(): for cls in classes: bpy.utils.register_class(cls) + bpy.types.WindowManager.mesh_check = PointerProperty( type=MeshCheckCollectionGroup ) @@ -354,6 +361,7 @@ def unregister(): 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) diff --git a/mesh_extra_tools/mesh_cut_faces.py b/mesh_extra_tools/mesh_cut_faces.py index 6674053e..cd710a07 100644 --- a/mesh_extra_tools/mesh_cut_faces.py +++ b/mesh_extra_tools/mesh_cut_faces.py @@ -10,7 +10,6 @@ bl_info = { import bpy import bmesh - from bpy.types import Operator from bpy.props import ( BoolProperty, @@ -99,7 +98,8 @@ def get_edge_rings(bm, keep_caps=True): 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) + 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: @@ -165,32 +165,32 @@ class MESH_xOT_cut_faces(Operator): SUBD_STRAIGHT_CUT = 3 num_cuts = IntProperty( - name="Number of Cuts", - default=1, - min=1, - max=100, - subtype='UNSIGNED' - ) + 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 - ) + 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' - ) + 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 - ) + name="Use Grid Fill", + description="Fill fully enclosed faces with a grid", + default=True + ) @classmethod def poll(cls, context): @@ -216,17 +216,18 @@ class MESH_xOT_cut_faces(Operator): try: edges = get_edge_rings(bm, keep_caps=True) if not edges: - self.report({'WARNING'}, "No suitable Face selection found. Operation cancelled") + 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)) - + 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'} diff --git a/mesh_extra_tools/mesh_edge_roundifier.py b/mesh_extra_tools/mesh_edge_roundifier.py index 00b5c4b9..961e8d0b 100644 --- a/mesh_extra_tools/mesh_edge_roundifier.py +++ b/mesh_extra_tools/mesh_edge_roundifier.py @@ -1,20 +1,20 @@ -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. +# 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. +# 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 LICENCE BLOCK ***** +# ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "Edge Roundifier", @@ -22,10 +22,10 @@ bl_info = { "author": "Piotr Komisarczyk (komi3D), PKHG", "version": (1, 0, 0), "blender": (2, 7, 3), - "location": "SPACE > Edge Roundifier or CTRL-E > Edge Roundifier or Tools > Addons > Edge Roundifier", + "location": "SPACE > Edge Roundifier or CTRL-E > " + "Edge Roundifier or Tools > Addons > Edge Roundifier", "description": "Mesh editing script allowing edge rounding", "wiki_url": "", - "tracker_url": "", "category": "Mesh" } @@ -38,9 +38,14 @@ from bpy.props import ( EnumProperty, IntProperty, ) - -from math import sqrt, acos, pi, radians, degrees, sin -from mathutils import Vector, Euler, Quaternion +from math import ( + sqrt, acos, pi, + radians, degrees, sin, + ) +from mathutils import ( + Vector, Euler, + Quaternion, + ) # CONSTANTS two_pi = 2 * pi @@ -79,7 +84,6 @@ class CalculationHelper: """ Constructor """ - def getLineCoefficientsPerpendicularToVectorInPoint(self, point, vector, plane): x, y, z = point xVector, yVector, zVector = vector @@ -92,7 +96,7 @@ class CalculationHelper: def getQuadraticRoots(self, coef): if len(coef) != 3: - return NaN # TODO lijenstina: Is this a valid return? + return None # Replaced NaN with None else: a, b, c = coef delta = b ** 2 - 4 * a * c @@ -155,7 +159,8 @@ class CalculationHelper: else: return None - def getLineCircleIntersectionsWhenXPerpendicular(self, edgeCenter, circleMidPoint, radius, plane): + 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 @@ -194,8 +199,8 @@ class CalculationHelper: # 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 Roundifier to work on - # komi3D> Normal and View coordinate systems. That would be great + # 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] @@ -260,206 +265,206 @@ class EdgeRoundifier(Operator): 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + name="Arc - Edge", + description="Connect Arcs to Edges", + default=False + ) connectArcs = BoolProperty( - name="Arcs", - description="Connect subsequent Arcs", - default=False - ) + name="Arcs", + description="Connect subsequent Arcs", + default=False + ) connectScaledAndBase = BoolProperty( - name="Scaled - Base Edge", - description="Connect Scaled to Base Edge", - default=False - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 paramaters set by the user\n" - "Reset - changes back the parameters to their default values" - ) + items=workModeItems, + name="", + default='Normal', + description="Normal work with the current given paramaters 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + items=edgeScaleCenterItems, + name="Edge scale center", + default='CENTER', + description="Center used for scaling the initial edge" + ) calc = CalculationHelper() sel = SelectionHelper() @@ -628,12 +633,8 @@ class EdgeRoundifier(Operator): bpy.ops.object.mode_set(mode='OBJECT') bm.to_mesh(mesh) bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='TOGGLE') - bpy.ops.mesh.select_all(action='INVERT') - bpy.ops.mesh.remove_doubles() - - bm.free() + return {'FINISHED'} def resetValues(self, workMode): @@ -753,10 +754,16 @@ class EdgeRoundifier(Operator): 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) + 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): @@ -768,9 +775,11 @@ class EdgeRoundifier(Operator): 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), - constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', - proportional_edit_falloff='SMOOTH', proportional_size=1) + bpy.ops.transform.translate( + value=-old_location, constraint_axis=(False, False, False), + constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', + proportional_edit_falloff='SMOOTH', proportional_size=1 + ) bpy.ops.object.mode_set(mode='EDIT') adjust_matrix = self.obj.matrix_parent_inverse bm = bmesh.from_edit_mesh(self.obj.data) @@ -797,9 +806,11 @@ class EdgeRoundifier(Operator): 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), - constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', - proportional_edit_falloff='SMOOTH', proportional_size=1) + bpy.ops.transform.translate( + value=old_location, constraint_axis=(False, False, False), + constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', + proportional_edit_falloff='SMOOTH', proportional_size=1 + ) bpy.ops.object.mode_set(mode='EDIT') def makeElliptic(self, bm, mesh, arcVertices, parameters): @@ -826,21 +837,37 @@ class EdgeRoundifier(Operator): [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation] = roundifyParams rotatedVerts = [] if parameters["rotateCenter"] == 'Edge': - rotatedVerts = self.rotateArcAroundSpinAxis(bm, mesh, spinnedVerts, parameters, edgeCenter) + rotatedVerts = self.rotateArcAroundSpinAxis( + bm, mesh, spinnedVerts, parameters, edgeCenter + ) elif parameters["rotateCenter"] == 'Spin': - rotatedVerts = self.rotateArcAroundSpinAxis(bm, mesh, spinnedVerts, parameters, chosenSpinCenter) + rotatedVerts = self.rotateArcAroundSpinAxis( + bm, mesh, spinnedVerts, parameters, chosenSpinCenter + ) elif parameters["rotateCenter"] == 'V1': - rotatedVerts = self.rotateArcAroundSpinAxis(bm, mesh, spinnedVerts, parameters, edge.verts[0].co) + 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) + 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) + self.connectArcTogetherWithEdge( + edge, offsetVerts2, bm, mesh, parameters + ) return offsetVerts2 def connectArcTogetherWithEdge(self, edge, arcVertices, bm, mesh, parameters): @@ -884,8 +911,10 @@ class EdgeRoundifier(Operator): def connectArcsTogether(self, arcs, bm, mesh, parameters): for i in range(0, len(arcs) - 1): - if arcs[i] is None or arcs[i + 1] is None: # in case on XZ or YZ there are no arcs drawn + # 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 @@ -906,7 +935,9 @@ class EdgeRoundifier(Operator): lastArcId = len(arcs) - 1 lastVertIdOfLastArc = len(arcs[lastArcId]) - 1 if parameters["drawArcCenters"]: - lastVertIdOfLastArc = lastVertIdOfLastArc - 1 # center gets added as last vert of arc + # center gets added as last vert of arc + lastVertIdOfLastArc = lastVertIdOfLastArc - 1 + V1 = arcs[lastArcId][lastVertIdOfLastArc].co V2 = arcs[0][0].co if parameters["connectArcsFlip"]: @@ -976,20 +1007,22 @@ class EdgeRoundifier(Operator): debugPrintNew(d_Plane, "PLANE: " + parameters["plane"]) lineAB = self.calc.getLineCoefficientsPerpendicularToVectorInPoint( - edgeCenter, edgeVector, - parameters["plane"] - ) + edgeCenter, edgeVector, + parameters["plane"] + ) circleMidPoint = V1 - circleMidPointOnPlane = self.calc.getCircleMidPointOnPlane(V1, parameters["plane"]) + 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 - ) + parameters["angleEnum"], radius, + angle, edgeLength + ) else: radius, angle = self.CalculateRadiusAndAngle(edgeLength) debugPrintNew(d_Radius_Angle, "RADIUS = " + str(radius) + " ANGLE = " + str(angle)) @@ -997,14 +1030,17 @@ class EdgeRoundifier(Operator): if angle != pi: # mode other than 180 if lineAB is None: roots = self.calc.getLineCircleIntersectionsWhenXPerpendicular( - edgeCenter, circleMidPointOnPlane, - radius, parameters["plane"] - ) + edgeCenter, circleMidPointOnPlane, + radius, parameters["plane"] + ) else: - roots = self.calc.getLineCircleIntersections(lineAB, circleMidPointOnPlane, radius) + roots = self.calc.getLineCircleIntersections( + lineAB, circleMidPointOnPlane, radius + ) if roots is None: - debugPrintNew(True, "[Edge Roundifier]: No centers were found. Change radius to higher value") + 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: @@ -1022,7 +1058,9 @@ class EdgeRoundifier(Operator): refObjectLocation = self.calc.getEdgeReference(edge, edgeCenter, parameters["plane"]) debugPrintNew(d_RefObject, parameters["refObject"], refObjectLocation) - chosenSpinCenter, otherSpinCenter = self.getSpinCenterClosestToRefCenter(refObjectLocation, roots) + chosenSpinCenter, otherSpinCenter = self.getSpinCenterClosestToRefCenter( + refObjectLocation, roots + ) if (parameters["entryMode"] == "Radius"): halfAngle = self.calc.getAngle(edgeCenter, chosenSpinCenter, circleMidPoint) @@ -1059,8 +1097,10 @@ class EdgeRoundifier(Operator): v0 = bm.verts.new(v0org.co) - result = bmesh.ops.spin(bm, geom=[v0], cent=chosenSpinCenter, axis=spinAxis, - angle=angle, steps=steps, use_duplicate=False) + 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 @@ -1088,21 +1128,21 @@ class EdgeRoundifier(Operator): if ((parameters["invertAngle"]) or (parameters["flip"])): if (midVertexDistance > midEdgeDistance): alternativeLastSpinVertIndices = self.alternateSpin( - bm, mesh, angle, chosenSpinCenter, - spinAxis, steps, v0, v1org, lastSpinVertIndices - ) + 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 - ) + 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 - ) + bm, mesh, angle, chosenSpinCenter, + spinAxis, steps, v0, v1org, lastSpinVertIndices + ) alternate = True self.sel.refreshMesh(bm, mesh) @@ -1120,19 +1160,19 @@ class EdgeRoundifier(Operator): # do some more testing here!!! if (angle == pi or angle == -pi): alternativeLastSpinVertIndices = self.alternateSpinNoDelete( - bm, mesh, -angle, chosenSpinCenter, - spinAxis, steps, v0, v1org, [] - ) + bm, mesh, -angle, chosenSpinCenter, + spinAxis, steps, v0, v1org, [] + ) elif alternate: alternativeLastSpinVertIndices = self.alternateSpinNoDelete( - bm, mesh, angle, otherSpinCenter, - spinAxis, steps, v0, v1org, [] - ) + bm, mesh, angle, otherSpinCenter, + spinAxis, steps, v0, v1org, [] + ) elif not alternate: alternativeLastSpinVertIndices = self.alternateSpinNoDelete( - bm, mesh, -angle, otherSpinCenter, - spinAxis, steps, v0, v1org, [] - ) + 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 @@ -1175,12 +1215,16 @@ class EdgeRoundifier(Operator): lastSpinVertIndices2 = self.getLastSpinVertIndices(steps, lastVertIndex2) return lastSpinVertIndices2 - def alternateSpin(self, bm, mesh, angle, chosenSpinCenter, spinAxis, steps, v0, v1org, lastSpinVertIndices): + 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) + 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) @@ -1245,7 +1289,8 @@ class EdgeRoundifier(Operator): self.a = angle_convert except: self.a = 180 # fallback - debugPrintNew(True, "CalculateRadiusAndAngleForAnglePresets problem with int conversion") + debugPrintNew(True, + "CalculateRadiusAndAngleForAnglePresets problem with int conversion") return self.CalculateRadiusAndAngle(edgeLength) diff --git a/mesh_extra_tools/mesh_edges_floor_plan.py b/mesh_extra_tools/mesh_edges_floor_plan.py new file mode 100644 index 00000000..bc4b1610 --- /dev/null +++ b/mesh_extra_tools/mesh_edges_floor_plan.py @@ -0,0 +1,384 @@ +# ##### 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 index 177fdb23..21276232 100644 --- a/mesh_extra_tools/mesh_edges_length.py +++ b/mesh_extra_tools/mesh_edges_length.py @@ -2,16 +2,15 @@ bl_info = { "name": "Set edges length", - "description": "edges length", + "description": "Edges length", "author": "Giuseppe De Marco [BlenderLab] inspired by NirenYang", "version": (0, 1, 0), - "blender": (2, 7, 0, 5), - "location": "[Toolbar][Tools][Mesh Tools]: set Length(Shit+Alt+E)", + "blender": (2, 7, 1), + "location": "Toolbar > Tools > Mesh Tools: set Length(Shit+Alt+E)", "warning": "", - "category": "Mesh", "wiki_url": "", - "tracker_url": "", -} + "category": "Mesh", + } import bpy import bmesh @@ -40,8 +39,8 @@ def get_edge_vector(edge): 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) @@ -66,51 +65,58 @@ class LengthSet(Operator): bl_options = {'REGISTER', 'UNDO'} old_length = FloatProperty( - name="Original length", - options={'HIDDEN'}, - ) + name="Original length", + options={'HIDDEN'}, + ) set_lenght_type = EnumProperty( - items=[ - ('manual', "Manual", "Input manually the desired Target Lenght"), - ('existing', "Existing Lenght", "Use existing geometry Edges' characteristics"), - ], - name="Set Type of Input", - ) + items=[ + ('manual', "Manual", + "Input manually the desired Target Lenght"), + ('existing', "Existing Lenght", + "Use existing geometry Edges' characteristics"), + ], + name="Set Type of Input", + ) target_length = FloatProperty( - name="Target Length", - description="Input a value for an Edges Lenght target", - default=1.00, - unit='LENGTH', - precision=5 - ) + name="Target Length", + description="Input a value for an Edges Lenght target", + default=1.00, + unit='LENGTH', + precision=5 + ) existing_lenght = 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 lenght 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 lenght" - ) + 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 lenght 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 lenght" + ) mode = EnumProperty( - items=[ - ('fixed', "Fixed", "Fixed"), - ('increment', "Increment", "Increment"), - ('decrement', "Decrement", "Decrement"), - ], - name="Mode" - ) + 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") + 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_lenghts = [] @@ -138,7 +144,7 @@ class LengthSet(Operator): layout.label("Mode:") layout.prop(self, "mode", text="") - layout.label("Resize Behavior") + layout.label("Resize Behavior:") layout.prop(self, "behaviour", text="") def get_existing_edge_lenght(self, bm): @@ -251,7 +257,8 @@ class LengthSet(Operator): if edge_length_debug: self.report({'INFO'}, ' - '.join(('vector ' + str(vector), - 'originary_vector ' + str(self.originary_edge_length_dict[verts_index]) + 'originary_vector ' + + str(self.originary_edge_length_dict[verts_index]) ))) verts = (edge.verts[0].co, edge.verts[1].co) @@ -281,18 +288,22 @@ class LengthSet(Operator): elif self.behaviour == 'unclockwise': if self.mode == 'increment': - edge.verts[1].co = verts[0] + (self.originary_edge_length_dict[verts_index] + vector) + 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) + 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) + 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) + edge.verts[1].co = \ + verts[0] + (self.originary_edge_length_dict[verts_index] - vector) else: edge.verts[0].co = verts[1] - vector diff --git a/mesh_extra_tools/mesh_edgetools.py b/mesh_extra_tools/mesh_edgetools.py index fde651cc..cc8b2dc8 100644 --- a/mesh_extra_tools/mesh_edgetools.py +++ b/mesh_extra_tools/mesh_edgetools.py @@ -1,7 +1,7 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# # 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 @@ -23,14 +23,13 @@ bl_info = { "name": "EdgeTools", "author": "Paul Marshall", - "version": (0, 9), + "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": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/Modeling/EdgeTools", - "tracker_url": "", "category": "Mesh"} @@ -92,10 +91,12 @@ 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 Interesect Line Face Test and debug prints available +# Set to True to have the debug prints available ENABLE_DEBUG = False @@ -112,6 +113,51 @@ def is_parallel(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 @@ -362,7 +408,9 @@ def intersect_line_face(edge, face, is_infinite=False, error=0.000002): 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: + 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 @@ -494,8 +542,9 @@ def intersect_line_face(edge, face, is_infinite=False, error=0.000002): # 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)) + (-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: @@ -515,13 +564,17 @@ def intersect_line_face(edge, face, is_infinite=False, error=0.000002): # 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) + 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) + 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) + t3 = (-a13 + a33 + (a13 - b13) * t + (a13 - a23) * + t12 + (a23 - a13 + b13 - b23) * t * t12) / (a33 - b33) else: - print("The second edge is a zero-length edge") + if ENABLE_DEBUG: + print("The second edge is a zero-length edge") return None # Calculate the point of intersection: @@ -576,7 +629,8 @@ def project_point_plane(pt, plane_co, plane_no): print("project_point_plane was called") proj_co = intersect_line_plane(pt, pt + plane_no, plane_co, plane_no) proj_ve = proj_co - pt - print("project_point_plane: proj_co is {}\nproj_ve is {}".format(proj_co, proj_ve)) + if ENABLE_DEBUG: + print("project_point_plane: proj_co is {}\nproj_ve is {}".format(proj_co, proj_ve)) return (proj_ve, proj_co) @@ -601,26 +655,29 @@ class Extend(Operator): bl_options = {'REGISTER', 'UNDO'} di1 = BoolProperty( - name="Forwards", - description="Extend the edge forwards", - default=True - ) + name="Forwards", + description="Extend the edge forwards", + default=True + ) di2 = BoolProperty( - name="Backwards", - description="Extend the edge backwards", - default=False - ) + 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 - ) + name="Length", + description="Length to extend the edge", + min=0.0, max=1024.0, + default=1.0 + ) def draw(self, context): layout = self.layout - layout.prop(self, "di1") - layout.prop(self, "di2") + + row = layout.row(align=True) + row.prop(self, "di1", toggle=True) + row.prop(self, "di2", toggle=True) + layout.prop(self, "length") @classmethod @@ -632,67 +689,76 @@ class Extend(Operator): return self.execute(context) def execute(self, context): - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() + try: + me = context.object.data + bm = bmesh.from_edit_mesh(me) + bm.normal_update() + + bEdges = bm.edges + bVerts = bm.verts - bEdges = bm.edges - bVerts = bm.verts + edges = [e for e in bEdges if e.select] + verts = [v for v in bVerts if v.select] - 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 + 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 = e.verts[1].co - vector - newE = bEdges.new((e.verts[1], v)) + v.co = verts[1].co - vector + e = bEdges.new((verts[1], v)) bEdges.ensure_lookup_table() else: - v.co = e.verts[0].co + vector - newE = bEdges.new((e.verts[0], v)) + 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 = e.verts[0].co + vector - newE = bEdges.new((e.verts[0], v)) + v.co = verts[0].co + vector + e = bEdges.new((verts[0], v)) bEdges.ensure_lookup_table() else: - v.co = e.verts[1].co - vector - newE = bEdges.new((e.verts[1], v)) + v.co = verts[1].co - vector + e = bEdges.new((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) - 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'} @@ -711,56 +777,60 @@ class Spline(Operator): 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' - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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("Edge 1:") - layout.prop(self, "ten1") - layout.prop(self, "flip1") + split = layout.split(percentage=0.8, align=True) + split.prop(self, "ten1") + split.prop(self, "flip1", text="", icon="ALIGN", toggle=True) + layout.label("Edge 2:") - layout.prop(self, "ten2") - layout.prop(self, "flip2") + split = layout.split(percentage=0.8, align=True) + split.prop(self, "ten2") + split.prop(self, "flip2", text="", icon="ALIGN", toggle=True) @classmethod def poll(cls, context): @@ -771,70 +841,88 @@ class Spline(Operator): return self.execute(context) def execute(self, context): - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() + try: + me = context.object.data + bm = bmesh.from_edit_mesh(me) + bm.normal_update() - bEdges = bm.edges - bVerts = bm.verts + bEdges = bm.edges + bVerts = bm.verts - seg = self.segments - edges = [e for e in bEdges if e.select] - verts = [edges[v // 2].verts[v % 2] for v in range(4)] + seg = self.segments + edges = [e for e in bEdges if e.select] - 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 not is_selected_enough(self, edges, 0, edges_n=2, verts_n=0, types="Edge"): + return {'CANCELLED'} - 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) + 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'} @@ -860,56 +948,55 @@ class Ortho(Operator): bl_options = {'REGISTER', 'UNDO'} vert1 = BoolProperty( - name="Vertice 1", - description="Enable edge creation for Vertice 1", - default=True - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + name="Positive", + description="Enable creation of positive direction edges", + default=True + ) neg = BoolProperty( - name="Negative", - description="Enable creation of negative direction edges", - default=True - ) + 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 - ) + 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 - ) - + 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" - ) + 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 @@ -932,8 +1019,10 @@ class Ortho(Operator): row.prop(self, "neg") layout.separator() - layout.prop(self, "angle") - layout.prop(self, "length") + + col = layout.column(align=True) + col.prop(self, "angle") + col.prop(self, "length") @classmethod def poll(cls, context): @@ -944,98 +1033,100 @@ class Ortho(Operator): return self.execute(context) def execute(self, context): - 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 = [] - - # Until I can figure out a better way of handling it: - if len(edges) < 2: - bpy.ops.object.editmode_toggle() - self.report({'ERROR_INVALID_INPUT'}, "You must select two edges") - return {'CANCELLED'} + try: + me = context.object.data + bm = bmesh.from_edit_mesh(me) + bm.normal_update() - verts = [edges[0].verts[0], - edges[0].verts[1], - edges[1].verts[0], - edges[1].verts[1]] + bVerts = bm.verts + bEdges = bm.edges + edges = [e for e in bEdges if e.select] + vectors = [] - cos = intersect_line_line(verts[0].co, verts[1].co, verts[2].co, verts[3].co) + if not is_selected_enough(self, edges, 0, edges_n=2, verts_n=0, types="Edge"): + return {'CANCELLED'} - # 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]): + 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 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() + "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) + 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'} @@ -1056,49 +1147,49 @@ class Shaft(Operator): # 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 - ) + 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 - ) + name="Edge", + description="Edge to shaft around", + min=0, max=1, + default=0 + ) flip = BoolProperty( - name="Flip Second Edge", - description="Flip the percieved direction of the second edge", - default=False - ) + name="Flip Second Edge", + description="Flip the percieved direction of the second edge", + default=False + ) radius = FloatProperty( - name="Radius", - description="Shaft Radius", - min=0.0, max=1024.0, - default=1.0 - ) + 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 - ) + 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 - ) + 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 - ) + 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 @@ -1126,221 +1217,228 @@ class Shaft(Operator): return self.execute(context) def execute(self, context): - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() + try: + me = context.object.data + bm = bmesh.from_edit_mesh(me) + bm.normal_update() - bFaces = bm.faces - bEdges = bm.edges - bVerts = bm.verts + bFaces = bm.faces + bEdges = bm.edges + bVerts = bm.verts - active = None - edges, verts = [], [] + active = None + edges, verts = [], [] - # Pre-caclulated values: - rotRange = [radians(self.start), radians(self.finish)] - rads = radians((self.finish - self.start) / self.segments) + # 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 + numV = self.segments + 1 + numE = self.segments - edges = [e for e in bEdges if e.select] + edges = [e for e in bEdges if e.select] - # Robustness check: there should at least be one edge selected - if len(edges) < 1: - bpy.ops.object.editmode_toggle() - self.report({'ERROR_INVALID_INPUT'}, - "At least one edge must be selected") - return {'CANCELLED'} + # 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 inital one, we flip them - # and have the GUI reflect that - if self.last_edge == self.edge: + # 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 inital 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): - if bm.select_history.active != edges[edge[0]]: - self.last_edge, self.edge = edge[1], edge[1] - edge = [edge[1], edge[0]] + 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: - bpy.ops.object.editmode_toggle() - self.report({'ERROR_INVALID_INPUT'}, - "Active geometry is not an edge") + 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]) + self.shaftType = 1 else: - bpy.ops.object.editmode_toggle() - self.report({'ERROR_INVALID_INPUT'}, - "Active geometry is not an edge") - 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 + verts.append(edges[0].verts[0]) + verts.append(edges[0].verts[1]) - # 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 = [] + 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 - # 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 orgin 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 + # 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 orgin 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 orgin 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 orgin 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 orgin 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)) + verts_out = [ + (verts[i].co - (matrices[j] * init_vec)) for i in range(2) for j in range(numV) + ] 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 orgin 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 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 orgin 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 + # We should have the coordinates for a bunch of new verts + # Now add the verts and build the edges and then the faces - newVerts = [] + 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])) + 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 - 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() + for i in range(numV): + e = bEdges.new((newVerts[i], newVerts[i + numV])) e.select = True - - # Faces: There is a problem with this right now - """ - for i in range(len(edges)): - for j in range(numE): + bEdges.ensure_lookup_table() + # Faces: + for i in range(numE): f = bFaces.new((newVerts[i], newVerts[i + 1], - newVerts[i + (numV * j) + 1], newVerts[i + (numV * j)])) + newVerts[i + numV + 1], newVerts[i + numV])) + bFaces.ensure_lookup_table() 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) + 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'} @@ -1354,25 +1452,25 @@ class Slice(Operator): bl_options = {'REGISTER', 'UNDO'} make_copy = BoolProperty( - name="Make Copy", - description="Make new vertices at intersection points instead of spliting the edge", - default=False - ) + name="Make Copy", + description="Make new vertices at intersection points instead of spliting the edge", + default=False + ) rip = BoolProperty( - name="Rip", - description="Split into two edges that DO NOT share an intersection vertex", - default=True - ) + 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 - ) + 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 - ) + name="Negative", + description="Remove the portion on the side opposite of the face normal", + default=False + ) def draw(self, context): layout = self.layout @@ -1393,136 +1491,142 @@ class Slice(Operator): return self.execute(context) def execute(self, context): - 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: - bpy.ops.object.editmode_toggle() - self.report({'ERROR_INVALID_INPUT'}, - "You must select a face as the cutting plane") - 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") + 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'} - if ENABLE_DEBUG: - dbg = 0 - print("Number of Edges: ", len(bEdges)) + # 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") - 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) + dbg = 0 + print("Number of Edges: ", len(bEdges)) + for e in bEdges: if ENABLE_DEBUG: - print("Intersection: ", intersection) + print("Looping through Edges - ", dbg) + dbg = dbg + 1 - # 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() + # Get the end verts on the edge: + v1 = e.verts[0] + v2 = e.verts[1] - if ENABLE_DEBUG: - print("new edges created", end='; ') + # 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 e.is_valid: - bEdges.remove(e) + if ENABLE_DEBUG: + print("Intersection: ", intersection) - bEdges.ensure_lookup_table() + # 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("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 - if ENABLE_DEBUG: - print("The Edge Loop has exited. Now to update the bmesh") - dbg = 0 + bmesh.update_edit_mesh(me) - 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'} @@ -1538,10 +1642,10 @@ class Project(Operator): bl_options = {'REGISTER', 'UNDO'} make_copy = BoolProperty( - name="Make Copy", - description="Make duplicates of the vertices instead of altering them", - default=False - ) + name="Make Copy", + description="Make duplicates of the vertices instead of altering them", + default=False + ) def draw(self, context): layout = self.layout @@ -1556,42 +1660,49 @@ class Project(Operator): return self.execute(context) def execute(self, context): - 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 + try: + me = context.object.data + bm = bmesh.from_edit_mesh(me) + bm.normal_update() + + bFaces = bm.faces + bVerts = bm.verts + + fVerts = [] - for v in bVerts: - if v.select: - if v in 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 - 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) + 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'} @@ -1609,25 +1720,25 @@ class Project_End(Operator): bl_options = {'REGISTER', 'UNDO'} make_copy = BoolProperty( - name="Make Copy", - description="Make a duplicate of the vertice instead of moving it", - default=False - ) + 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 - ) + 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 - ) + 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 - ) + name="Project along normal", + description="Use the plane's normal as the projection direction", + default=False + ) def draw(self, context): layout = self.layout @@ -1646,134 +1757,77 @@ class Project_End(Operator): return self.execute(context) def execute(self, context): - 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) - - return {'FINISHED'} - + try: + me = context.object.data + bm = bmesh.from_edit_mesh(me) + bm.normal_update() -# For testing the mess that is "intersect_line_face" for possible math errors -# This will NOT be directly exposed to end users: it will always require -# switching the ENABLE_DEBUG global to True -# So far no errors have been found. Thanks to anyone who tests and reports bugs! -# @todo: this is will be removed before final merge + bFaces = bm.faces + bEdges = bm.edges + bVerts = bm.verts -class Intersect_Line_Face(Operator): - bl_idname = "mesh.edgetools_ilf" - bl_label = "Interesect Line Face Test" - bl_description = "TEST ONLY: Intersect line face" - bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + fVerts = [] - @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): - # Switch to true to have access to this operator: - if not ENABLE_DEBUG: - self.report({'ERROR_INVALID_INPUT'}, - "This is for debugging only: you should not be able to run this!") - return {'CANCELLED'} - - me = context.object.data - bm = bmesh.from_edit_mesh(me) - bm.normal_update() - - bFaces = bm.faces - bEdges = bm.edges - bVerts = bm.verts - - face = None - for f in bFaces: - if f.select: - face = f - break + # 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 - edge = None - for e in bEdges: - if e.select and e not in face.edges: - edge = e - 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 - point = intersect_line_face(edge, face, True) + bmesh.update_edit_mesh(me) - if point is not None: - new = bVerts.new() - new.co = point - bVerts.ensure_lookup_table() - else: - bpy.ops.object.editmode_toggle() - self.report({'ERROR_INVALID_INPUT'}, "point was \"None\"") + except Exception as e: + error_handlers(self, "mesh.edgetools_project_end", e, + reports="Project (End Point) Operator failed", func=False) return {'CANCELLED'} - bmesh.update_edit_mesh(me) - return {'FINISHED'} @@ -1793,14 +1847,10 @@ class VIEW3D_MT_edit_mesh_edgetools(Menu): layout.operator("mesh.edgetools_project") layout.operator("mesh.edgetools_project_end") - if ENABLE_DEBUG: - # For internal testing ONLY: - layout.separator() - layout.operator("mesh.edgetools_ilf") # define classes for registration -classes = [ +classes = ( VIEW3D_MT_edit_mesh_edgetools, Extend, Spline, @@ -1809,20 +1859,19 @@ classes = [ Slice, Project, Project_End, - Intersect_Line_Face - ] + ) # registering and menu integration def register(): - for c in classes: - bpy.utils.register_class(c) + for cls in classes: + bpy.utils.register_class(cls) # unregistering and removing menus def unregister(): - for c in classes: - bpy.utils.unregister_class(c) + for cls in classes: + bpy.utils.unregister_class(cls) if __name__ == "__main__": diff --git a/mesh_extra_tools/mesh_extrude_and_reshape.py b/mesh_extra_tools/mesh_extrude_and_reshape.py index 14d9d744..8716bfb1 100644 --- a/mesh_extra_tools/mesh_extrude_and_reshape.py +++ b/mesh_extra_tools/mesh_extrude_and_reshape.py @@ -1,4 +1,4 @@ -### BEGIN GPL LICENSE BLOCK ##### +# ##### 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 @@ -25,14 +25,17 @@ bl_info = { "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", - "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", + "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, bmesh +import bpy +import bmesh from mathutils.geometry import intersect_line_line -from bpy.props import FloatProperty +from bpy.types import Operator + class BVHco(): i = 0 @@ -43,7 +46,8 @@ class BVHco(): c2y = 0.0 c2z = 0.0 -def edges_BVH_overlap(bm, edges, epsilon = 0.0001): + +def edges_BVH_overlap(bm, edges, epsilon=0.0001): bco = set() for e in edges: bvh = BVHco() @@ -113,19 +117,19 @@ def edges_BVH_overlap(bm, edges, epsilon = 0.0001): overlap[e1] = oget(e1, set()).union({e2}) return overlap -def intersect_edges_edges(overlap, precision = 4): + +def intersect_edges_edges(overlap, precision=4): epsilon = .1**precision fpre_min = -epsilon - fpre_max = 1+epsilon + fpre_max = 1 + epsilon splits = {} sp_get = splits.get new_edges1 = set() new_edges2 = set() targetmap = {} for edg1 in overlap: - #print("***", ed1.index, "***") + # print("***", ed1.index, "***") for edg2 in overlap[edg1]: - #print('loop', ed2.index) a1 = edg1.verts[0] a2 = edg1.verts[1] b1 = edg2.verts[0] @@ -133,7 +137,7 @@ def intersect_edges_edges(overlap, precision = 4): # test if are linked if a1 in {b1, b2} or a2 in {b1, b2}: - #print('linked') + # print('linked') continue aco1, aco2 = a1.co, a2.co @@ -141,23 +145,23 @@ def intersect_edges_edges(overlap, precision = 4): 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 + 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) + 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] + fac1 = f[max1] / v[max1] - v = bco2-bco1 + v = bco2 - bco1 f = p2 - bco1 - x,y,z = abs(v.x), abs(v.y), abs(v.z) + 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] + fac2 = f[max2] / v[max2] if fpre_min <= fac1 <= fpre_max: - #print(edg1.index, 'can intersect', edg2.index) + # print(edg1.index, 'can intersect', edg2.index) ed1 = edg1 elif edg1 in splits: @@ -168,21 +172,21 @@ def intersect_edges_edges(overlap, precision = 4): vco1 = a1.co vco2 = a2.co - v = vco2-vco1 + v = vco2 - vco1 f = p1 - vco1 - fac1 = f[max1]/v[max1] + fac1 = f[max1] / v[max1] if fpre_min <= fac1 <= fpre_max: - #print(e.index, 'can intersect', edg2.index) + # print(e.index, 'can intersect', edg2.index) break else: - #print(edg1.index, 'really does not intersect', edg2.index) + # print(edg1.index, 'really does not intersect', edg2.index) continue else: - #print(edg1.index, 'not intersect', edg2.index) + # print(edg1.index, 'not intersect', edg2.index) continue if fpre_min <= fac2 <= fpre_max: - #print(ed1.index, 'actually intersect', edg2.index) + # print(ed1.index, 'actually intersect', edg2.index) ed2 = edg2 elif edg2 in splits: @@ -193,17 +197,17 @@ def intersect_edges_edges(overlap, precision = 4): vco1 = b1.co vco2 = b2.co - v = vco2-vco1 + v = vco2 - vco1 f = p2 - vco1 - fac2 = f[max2]/v[max2] + fac2 = f[max2] / v[max2] if fpre_min <= fac2 <= fpre_max: - #print(ed1.index, 'actually intersect', e.index) + # print(ed1.index, 'actually intersect', e.index) break else: - #print(ed1.index, 'really does not intersect', ed2.index) + # print(ed1.index, 'really does not intersect', ed2.index) continue else: - #print(ed1.index, 'not intersect', edg2.index) + # print(ed1.index, 'not intersect', edg2.index) continue new_edges1.add(ed1) @@ -227,30 +231,28 @@ def intersect_edges_edges(overlap, precision = 4): new_edges2.add(ne2) splits[edg2] = sp_get(edg2, set()).union({ne2}) - if nv1 != nv2: #necessary? + if nv1 != nv2: # necessary? targetmap[nv1] = nv2 - #else: - #print('not coplanar') - #else: - #print("parallel or collinear") + return new_edges1, new_edges2, targetmap -class Extrude_and_Reshape(bpy.types.Operator): - """Push and pull face entities to sculpt 3d models""" + +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 is not 'EDIT_MESH' + return context.mode is not '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 == True: + if face.select is True: sface = face break else: @@ -259,13 +261,13 @@ class Extrude_and_Reshape(bpy.types.Operator): 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]) - + 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: @@ -274,9 +276,11 @@ class Extrude_and_Reshape(bpy.types.Operator): 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() + """ + 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) @@ -285,11 +289,11 @@ class Extrude_and_Reshape(bpy.types.Operator): for f in rlfe: try: nf = bmesh.utils.face_split(f, v1, v2) - #sp_faces1.update({f, nf[0]}) + # sp_faces1.update({f, nf[0]}) except: pass - #sp_faces2 = set() + # sp_faces2 = set() for e in new_edges2: lfe = set(e.link_faces) v1, v2 = e.verts @@ -298,7 +302,7 @@ class Extrude_and_Reshape(bpy.types.Operator): rlfe = lf1.intersection(lf2) for f in rlfe.difference(lfe): nf = bmesh.utils.face_split(f, v1, v2) - #sp_faces2.update({f, nf[0]}) + # sp_faces2.update({f, nf[0]}) bmesh.update_edit_mesh(self.mesh, tessface=True, destructive=True) return {'FINISHED'} @@ -315,7 +319,7 @@ class Extrude_and_Reshape(bpy.types.Operator): selection = self.bm.select_history[-1] except: for face in self.bm.faces: - if face.select == True: + if face.select is True: selection = face break else: @@ -325,22 +329,27 @@ class Extrude_and_Reshape(bpy.types.Operator): return {'FINISHED'} else: face = selection - #face.select = False + # 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: + 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]) + 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) + dfaces = bmesh.ops.dissolve_edges( + self.bm, edges=geom, use_verts=True, use_face_split=False + ) bmesh.update_edit_mesh(self.mesh, tessface=True, destructive=True) - bpy.ops.transform.translate('INVOKE_DEFAULT', constraint_axis=(False, False, True), constraint_orientation='NORMAL', release_confirm=True) + bpy.ops.transform.translate( + 'INVOKE_DEFAULT', constraint_axis=(False, False, True), + constraint_orientation='NORMAL', release_confirm=True + ) context.window_manager.modal_handler_add(self) @@ -348,18 +357,22 @@ class Extrude_and_Reshape(bpy.types.Operator): self.confirm = False return {'RUNNING_MODAL'} -def operator_draw(self,context): + +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 index 81b9ffb7..b713d481 100644 --- a/mesh_extra_tools/mesh_fastloop.py +++ b/mesh_extra_tools/mesh_fastloop.py @@ -20,12 +20,11 @@ bl_info = { "name": "Fast Loop", "description": "Add loops fast", "author": "Andy Davies (metalliandy)", - "version": (0, 16), + "version": (0, 1, 7), "blender": (2, 5, 6), "location": "Tool Shelf", "warning": "", "wiki_url": "", - "tracker_url": "", "category": "Mesh" } diff --git a/mesh_extra_tools/mesh_filletplus.py b/mesh_extra_tools/mesh_filletplus.py index 025de0b0..442adb54 100644 --- a/mesh_extra_tools/mesh_filletplus.py +++ b/mesh_extra_tools/mesh_filletplus.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -# ***** BEGIN GPL LICENSE BLOCK ***** -# +# ##### 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 @@ -17,8 +16,7 @@ # 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 LICENCE BLOCK ***** - +# ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "FilletPlus", @@ -29,7 +27,6 @@ bl_info = { "description": "", "warning": "", "wiki_url": "", - "tracker_url": "", "category": "Mesh"} @@ -42,7 +39,10 @@ from bpy.props import ( from bpy.types import Operator import bmesh from mathutils import Matrix -from math import cos, pi, degrees, sin, tan +from math import ( + cos, pi, sin, + degrees, tan, + ) def list_clear_(l): @@ -298,36 +298,36 @@ class MESH_OT_fillet_plus(Operator): 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + name="Radius", + description="Use radius for the size of the filleted corners", + default=False + ) @classmethod def poll(cls, context): @@ -338,8 +338,8 @@ class MESH_OT_fillet_plus(Operator): layout = self.layout if f_buf.check is False: - layout.label(text="Angle equal to 0 or 180", icon="INFO") - layout.label("Can not fillet") + 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: @@ -349,6 +349,7 @@ class MESH_OT_fillet_plus(Operator): layout.prop(self, "adj") layout.label("Number of sides:") layout.prop(self, "n") + if self.n > 1: row = layout.row(align=False) row.prop(self, "out") diff --git a/mesh_extra_tools/mesh_help.py b/mesh_extra_tools/mesh_help.py index 7d06c40c..991aee87 100644 --- a/mesh_extra_tools/mesh_help.py +++ b/mesh_extra_tools/mesh_help.py @@ -1,13 +1,13 @@ # gpl authors: lijenstina, meta-androcto # Note: this script contains the Help Operator used by the various functions -# Usage: add a key string the dictionary with the list of strings to pass to labels in this file +# 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 varibles -# Example (variable prop): -# prop = layout.row("mesh.extra_tools_help") -# prop.help_ids = "default" -# prop.popup_size = 400 +# Example (with using the variable props): +# props = layout.row("mesh.extra_tools_help") +# props.help_ids = "default" +# props.popup_size = 400 import bpy @@ -24,16 +24,16 @@ class MESH_OT_extra_tools_help(Operator): bl_options = {'REGISTER'} help_ids = StringProperty( - name="ID of the Operator to display", - options={'HIDDEN'}, - default="default" - ) + 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, - ) + name="Size of the Help Pop-up Menu", + default=350, + min=100, + max=600, + ) def draw(self, context): layout = self.layout @@ -55,144 +55,174 @@ def help_custom_draw(identifier="default"): # 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", - "Before 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 be kept as an option", - "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 whose all faces share 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_to_wall": [ - "To use:", - "Extrudes flat along edges", - "Adds depth on both sides of an edge wire", - "Limitation:", - "Works best on Flat surfaces i.e. Ground Plan like 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", - ], - } + 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] @@ -202,13 +232,11 @@ def help_custom_draw(identifier="default"): # register def register(): - bpy.utils.register_module(__name__) - pass + bpy.utils.register_class(MESH_OT_extra_tools_help) def unregister(): - bpy.utils.unregister_module(__name__) - pass + bpy.utils.unregister_class(MESH_OT_extra_tools_help) if __name__ == "__main__": diff --git a/mesh_extra_tools/mesh_mextrude_plus.py b/mesh_extra_tools/mesh_mextrude_plus.py index 0ae161b7..de5f561b 100644 --- a/mesh_extra_tools/mesh_mextrude_plus.py +++ b/mesh_extra_tools/mesh_mextrude_plus.py @@ -15,11 +15,11 @@ # 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 coörds, initial and per step noise +# normal rotation, probability, scaled offset, object coords, initial and per step noise bl_info = { @@ -31,7 +31,6 @@ bl_info = { "description": "Repeat extrusions from faces to create organic shapes", "warning": "", "wiki_url": "", - "tracker_url": "https://developer.blender.org/T28570", "category": "Mesh"} @@ -41,32 +40,38 @@ import random from bpy.types import Operator from random import gauss from math import radians -from mathutils import Euler, Vector +from mathutils import ( + Euler, Vector, + ) from bpy.props import ( FloatProperty, IntProperty, BoolProperty, - EnumProperty, ) + 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)) @@ -80,142 +85,142 @@ class MExtrude(Operator): 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + name="Seed", + min=-9999, max=9999, + default=0, + description="Seed to feed random values" + ) opt1 = BoolProperty( - name="Polygon coördinates", - default=True, - description="Polygon coördinates, Object coördinates" - ) + name="Polygon coordinates", + default=True, + description="Polygon coordinates, Object coordinates" + ) opt2 = BoolProperty( - name="Proportional offset", - default=False, - description="Scale * Offset" - ) + 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" - ) + 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" - ) + name="Per step scale noise", + default=False, + description="Per step scale noise, Initial scale noise" + ) @classmethod def poll(cls, context): @@ -235,9 +240,9 @@ class MExtrude(Operator): 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.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) @@ -288,10 +293,8 @@ class MExtrude(Operator): # extrusion loop for r in range(self.num): - - ## random probability % for extrusions - if self.var4 > int(random.random()*100): - + # random probability % for extrusions + if self.var4 > int(random.random() * 100): nf = of.copy() nf.normal_update() no = nf.normal.copy() @@ -348,7 +351,8 @@ class MExtrude(Operator): bpy.ops.object.mode_set(mode=om) if not len(sel): - self.report({"WARNING"}, "No suitable Face selection found. Operation cancelled") + self.report({"WARNING"}, + "No suitable Face selection found. Operation cancelled") return {'CANCELLED'} return {'FINISHED'} diff --git a/mesh_extra_tools/mesh_offset_edges.py b/mesh_extra_tools/mesh_offset_edges.py index b1f9cf09..2168e76c 100644 --- a/mesh_extra_tools/mesh_offset_edges.py +++ b/mesh_extra_tools/mesh_offset_edges.py @@ -1,20 +1,20 @@ -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. +# 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. +# 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 LICENCE BLOCK ***** +# ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "Offset Edges", @@ -24,8 +24,8 @@ bl_info = { "location": "VIEW3D > Edge menu(CTRL-E) > Offset Edges", "description": "Offset Edges", "warning": "", - "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Modeling/offset_edges", - "tracker_url": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" + "Py/Scripts/Modeling/offset_edges", "category": "Mesh"} import bpy @@ -510,103 +510,103 @@ class OffsetEdges(Operator): 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + 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 - ) + name="Flip Angle", + default=False, + description="Flip Angle", + update=use_cashes + ) follow_face = BoolProperty( - name="Follow Face", - default=False, - description="Offset along faces around" - ) + 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" - ) + 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" - ) + 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" - ) + 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'} - ) + 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'} - ) + 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 - ) + 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 diff --git a/mesh_extra_tools/mesh_pen_tool.py b/mesh_extra_tools/mesh_pen_tool.py index d0853088..fbdc450b 100644 --- a/mesh_extra_tools/mesh_pen_tool.py +++ b/mesh_extra_tools/mesh_pen_tool.py @@ -1,33 +1,32 @@ # -*- coding: utf-8 -*- -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. +# 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. +# 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 LICENCE BLOCK ***** +# ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "Pen Tool", "author": "zmj100", - "version": (0, 2, 9), + "version": (0, 3, 1), "blender": (2, 78, 0), "location": "View3D > Tool Shelf", "description": "", "warning": "", "wiki_url": "", - "tracker_url": "", "category": "Mesh", } @@ -47,8 +46,14 @@ from bpy.props import ( PointerProperty, BoolProperty ) -from bpy_extras.view3d_utils import region_2d_to_location_3d, location_3d_to_region_2d -from mathutils import Vector, Matrix +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 @@ -117,10 +122,10 @@ def draw_callback_px(self, context): # 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 - ) + 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.user_preferences.system.dpi) blf.draw(font_id, @@ -135,17 +140,19 @@ def draw_callback_px(self, context): 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) + 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 - ) + 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.user_preferences.system.dpi) @@ -234,9 +241,9 @@ def draw_callback_px(self, context): pass else: loc_4 = location_3d_to_region_2d( - context.region, context.space_data.region_3d, - pt_buf.list_m_loc_3d[h] - ) + 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.user_preferences.system.dpi) @@ -252,41 +259,41 @@ def draw_callback_px(self, context): 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 - ) + 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 - ) + 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 - ) + name="Angles", + description="Display All Angles on Drawn Edges", + default=False + ) b1 = BoolProperty( - name="Edge Length", - description="Display All Lenghts of Drawn Edges", - default=False - ) + name="Edge Length", + description="Display All Lenghts of Drawn Edges", + default=False + ) b2 = BoolProperty( - name="Mouse Location 3D", - description="Display the location coordinates of the mouse cursor", - default=False - ) + 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 - ) + name="Restore View", + description="After the tool has finished, is the Viewport restored\n" + "to it's previous state", + default=True + ) class pt_buf(): @@ -314,25 +321,27 @@ class pen_tool_panel(Panel): 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("Pen Tool Active") + layout.label(text="Pen Tool Active", icon="INFO") else: - layout.label("Font:") - - row = layout.split(0.50, align=True) - row.prop(context.scene.pen_tool_props, "fs", text="Size", slider=True) - row.prop(context.scene.pen_tool_props, "a", text="Alpha", slider=True) - - layout.prop(context.scene.pen_tool_props, "b0", text="Angles") - layout.prop(context.scene.pen_tool_props, "b1", text="Edge Length") - layout.prop(context.scene.pen_tool_props, "b2", text="Mouse Location 3D") - layout.prop(context.scene.pen_tool_props, "restore_view", text="Restore View") - - row1 = layout.split(0.80, align=True) - row1.operator("pen_tool.operator", text="Draw") - row1.operator("mesh.extra_tools_help", + col = layout.column(align=True) + col.label("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("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" @@ -343,10 +352,10 @@ class pen_tool_operator(Operator): bl_options = {"REGISTER", "UNDO", "INTERNAL"} text_location = IntProperty( - name="", - default=0, - options={'HIDDEN'} - ) + name="", + default=0, + options={'HIDDEN'} + ) @classmethod def poll(cls, context): @@ -498,7 +507,8 @@ class pen_tool_operator(Operator): self.text_location = region.width - 100 if overlap: for region in context.area.regions: - if region.type == 'TOOL_PROPS': + # 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': @@ -513,20 +523,22 @@ class pen_tool_operator(Operator): pt_buf.sws = 'on' return {'RUNNING_MODAL'} else: - self.report({'WARNING'}, "Pen Tool: View3D not found, operation cancelled") + self.report({'WARNING'}, "Pen Tool: Operation Cancelled. View3D not found") return {'CANCELLED'} -class_list = [pen_tool_panel, - pen_tool_operator, - pen_tool_properties - ] +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) + # 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}, ()), diff --git a/mesh_extra_tools/mesh_select_tools/__init__.py b/mesh_extra_tools/mesh_select_tools/__init__.py index 2d066c82..74d42f1c 100644 --- a/mesh_extra_tools/mesh_select_tools/__init__.py +++ b/mesh_extra_tools/mesh_select_tools/__init__.py @@ -15,32 +15,34 @@ # 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, CoDEmanX, 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), + "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": "", - "tracker_url": "", "category": "Mesh" } if "bpy" in locals(): - import imp - imp.reload(mesh_select_by_direction) - imp.reload(mesh_select_by_edge_length) - imp.reload(mesh_select_by_pi) - imp.reload(mesh_select_by_type) - imp.reload(mesh_select_connected_faces) - imp.reload(mesh_index_select) - imp.reload(mesh_selection_topokit) - imp.reload(mesh_info_select) + 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 diff --git a/mesh_extra_tools/mesh_select_tools/mesh_index_select.py b/mesh_extra_tools/mesh_select_tools/mesh_index_select.py index cd558e85..492ff825 100644 --- a/mesh_extra_tools/mesh_select_tools/mesh_index_select.py +++ b/mesh_extra_tools/mesh_select_tools/mesh_index_select.py @@ -22,7 +22,7 @@ from bpy.props import ( class SelVertEdgeFace(Operator): bl_idname = "mesh.select_vert_edge_face_index" bl_label = "Select mesh index" - bl_description = "Select Vertices, Edges, Faces by index" + bl_description = "Select Vertices, Edges, Faces by their indices" bl_options = {"REGISTER", "UNDO"} select_type = EnumProperty( @@ -59,7 +59,6 @@ class SelVertEdgeFace(Operator): 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"} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_info_select.py b/mesh_extra_tools/mesh_select_tools/mesh_info_select.py index 448bdbdc..c274cda2 100644 --- a/mesh_extra_tools/mesh_select_tools/mesh_info_select.py +++ b/mesh_extra_tools/mesh_select_tools/mesh_info_select.py @@ -17,12 +17,58 @@ # ##### END GPL LICENSE BLOCK ##### # By CoDEmanX +# updated by lijenstina import bpy -from bpy.types import ( - Panel, - Operator, - ) +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): @@ -41,28 +87,20 @@ class DATA_PT_info_panel(Panel): def draw(self, context): layout = self.layout - - ob = context.active_object - + 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 = "" - tris = quads = ngons = 0 - - for p in ob.data.polygons: - count = p.loop_total - if count == 3: - tris += 1 - elif count == 4: - quads += 1 - else: - ngons += 1 - info_str = " Ngons: %i Quads: %i Tris: %i" % (ngons, quads, tris) + box = layout.box() + col = box.column() + split = col.split(percentage=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") - col = layout.column() - split = col.split(0.9) - - split.label(info_str, icon='MESH_DATA') - split.operator("mesh.refresh_info_panel", text="", icon="FILE_REFRESH") + if check_used: + info_str = check_the_obj_polycount(context, check_delay) + col.label(info_str, icon='MESH_DATA') col = layout.column() col.label("Select faces by type:") @@ -71,38 +109,3 @@ class DATA_PT_info_panel(Panel): 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" - - -class MESH_OT_refresh_info_panel(Operator): - bl_idname = "mesh.refresh_info_panel" - bl_label = "Refresh" - bl_description = ("Refresh the info panel by switching to Object mode and back\n" - "Limitation: the information doesn't account modifiers\n" - "Be careful with usage if you need the Redo History in Edit Mode") - bl_options = {"REGISTER", "INTERNAL"} - - @classmethod - def poll(self, context): - return (context.active_object is not None and - context.active_object.type == 'MESH') - - def invoke(self, context, event): - return self.execute(context) - - def execute(self, context): - try: - mode = bpy.context.active_object.mode - - # switch to Object mode and restore selection - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode=mode) - - return {'FINISHED'} - except: - import traceback - traceback.print_exc() - - self.report({'WARNING'}, - "The refresh could not be performed (Check the console for more info)") - - return {'CANCELLED'} 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 index 7a82b949..799f6002 100644 --- a/mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py +++ b/mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py @@ -1,43 +1,31 @@ # Copyright (C) 2011, Dolf Veenvliet # Extrude a selection from a mesh multiple times -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. # -# 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. # -# 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 LICENCE BLOCK ***** -''' -bl_info = { - "name": "Select by direction", - "author": "Dolf Veenvliet", - "version": (1,), - "blender": (2, 56, 0), - "location": "View3D > Select", - "description": "Select all items whose normals face a certain direction", - "warning": "", - "wiki_url": "", - "tracker_url": "", - "category": "Mesh"} +# ##### 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 @@ -175,39 +163,39 @@ class Select_init(Operator): 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 - ) + 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' - ) + 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 - ) + 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' - ) + items=spaces, + name="Space", + description="The space to interpret the directions in", + default='LOC' + ) @classmethod def poll(cls, context): 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 index 8430355c..b77f9750 100644 --- 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 @@ -1,47 +1,33 @@ # mesh_select_by_edge_length.py Copyright (C) 2011, Dolf Veenvliet # Extrude a selection from a mesh multiple times -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. +# 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. +# 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 LICENCE BLOCK ***** - -''' -bl_info = { - "name": "Select by edge length", - "author": "Dolf Veenvliet", - "version": (1,), - "blender": (2, 56, 0), - "location": "View3D > Select", - "description": "Select all items whose scale/length/surface matches a certain edge length", - "warning": "", - "wiki_url": "", - "tracker_url": "", - "category": "Mesh"} +# ##### END GPL LICENSE BLOCK ##### """ Usage: - -Launch from from "Select -> By edge length" + 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 ( @@ -198,43 +184,43 @@ class Select_init(bpy.types.Operator): 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 - ) + 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 lenght", - default='EQUAL' - ) + items=sizes, + name="Edge comparison", + description="Choose the relation to set edge lenght", + default='EQUAL' + ) extend = BoolProperty( - name="Extend", - description="Extend the current selection", - default=False - ) + name="Extend", + description="Extend the current selection", + default=False + ) start_new = BoolProperty( - name="Fresh Start", - default=False, - description="Start from no previous selection" - ) + 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' - ) + items=spaces, + name="Space", + description="The space to interpret the directions in", + default='LOC' + ) @classmethod def poll(cls, context): 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 index 6f2d4418..f002d0d1 100644 --- a/mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py +++ b/mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py @@ -1,45 +1,31 @@ # mesh_select_by_pi.py Copyright (C) 2011, Dolf Veenvliet # Extrude a selection from a mesh multiple times -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. +# 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. +# 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 LICENCE BLOCK ***** - -''' -bl_info = { - "name": "Select by pi", - "author": "Dolf Veenvliet", - "version": 1, - "blender": (2, 56, 0), - "location": "View3D > Select", - "description": "Select fake random based on pi", - "warning": "", - "wiki_url": "", - "tracker_url": "", - "category": "Mesh"} +# ##### 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 @@ -179,20 +165,20 @@ class Select_init(Operator): bl_options = {'REGISTER', 'UNDO'} e = BoolProperty( - name="Use e", - description="Use e as the base of selection instead of pi", - default=False - ) + 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 - ) + name="Invert", + description="Invert the selection result", + default=False + ) extend = BoolProperty( - name="Extend", - description="Extend the current selection", - default=False - ) + name="Extend", + description="Extend the current selection", + default=False + ) start_new = BoolProperty( name="Fresh Start", default=False, 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 index b718d453..534740a1 100644 --- a/mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py +++ b/mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py @@ -33,17 +33,17 @@ class DATA_OP_facetype_select(Operator): 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" - ) + 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 - ) + name="Extend", + description="Extend Selection", + default=False + ) @classmethod def poll(cls, context): @@ -68,11 +68,8 @@ class DATA_OP_facetype_select(Operator): return {'FINISHED'} except Exception as e: - print("\n[Select by face type]\nERROR:\n") - - import traceback - traceback.printexc() - - self.report('WARNING', "Face selection could not be performed (Check the console for more info)") + 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 index ff131f9c..1198f9ff 100644 --- a/mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py +++ b/mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py @@ -1,36 +1,23 @@ # Copyright (C) 2011, Dolf Veenvliet # Extrude a selection from a mesh multiple times -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. +# 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. +# 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 LICENCE BLOCK ***** - -''' - bl_info = { - "name": "Select connected faces", - "author": "Dolf Veenvliet", - "version": (1,), - "blender": (2, 56, 0), - "location": "View3D > Select", - "description": "Select all faces connected to the current selection", - "warning": "", - "wiki_url": "", - "tracker_url": "", - "category": "Mesh"} +# ##### END GPL LICENSE BLOCK ##### """ Usage: @@ -40,7 +27,7 @@ Additional links: Author Site: http://www.macouno.com e-mail: dolf {at} macouno {dot} com """ -''' + import bpy from bpy.types import Operator @@ -121,16 +108,17 @@ class Select_init(Operator): # Iterations iterations = IntProperty( - name="Iterations", - default=1, - min=0, max=300, - soft_min=0, soft_max=100 - ) + 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 - ) + name="Extend", + description="Extend the current selection", + default=False + ) @classmethod def poll(cls, context): diff --git a/mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py b/mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py index 232714c4..c787b305 100644 --- a/mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py +++ b/mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py @@ -21,11 +21,10 @@ bl_info = { "author": "dustractor", "version": (2, 0), "blender": (2, 60, 0), - "location": "edit mesh vertices/edges/faces menus", + "location": "Edit mesh > Vertices/ Edges/ Faces menus", "description": "", "warning": "", "wiki_url": "", - "tracker_url": "", "category": "Mesh"} @@ -88,13 +87,15 @@ class MESH_OT_vneighbors_edgewise(meshpoller, Operator): prev_state = None if not prev_state: - selected_vert_indices = filter(lambda _: mesh.vertices[_].select, - range(len(mesh.vertices))) + 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)) - ) + 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]: @@ -242,7 +243,8 @@ class MESH_OT_eneighbors_shared_v(meshpoller, Operator): 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 + 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") @@ -279,10 +281,12 @@ class MESH_OT_eneighbors_shared_f(meshpoller, Operator): 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) diff --git a/mesh_extra_tools/mesh_to_wall.py b/mesh_extra_tools/mesh_to_wall.py deleted file mode 100644 index bc6c753b..00000000 --- a/mesh_extra_tools/mesh_to_wall.py +++ /dev/null @@ -1,258 +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 ##### - - -bl_info = { - "name": "Mesh to wall", - "author": "luxuy_BlenderCN", - "version": (0.8), - "blender": (2, 71, 0), - "location": "View3D > EditMode > Mesh", - "description": "Make wall from single mesh lines.", - "url": "https://luxuy.github.io/BlenderAddons/Mesh-to-wall/Mesh_to_wall.html", - "category": "Mesh"} - -import bpy -import bmesh -from bpy.types import Operator -from bpy.props import FloatProperty -from math import sin, radians -from mathutils import Vector, Quaternion - - -def link_verts(bm, ver_index): - linked_verts = [] - bm.verts.ensure_lookup_table() - v = bm.verts[ver_index] - for e in v.link_edges: - linked_verts.append(e.verts[1].index) - linked_verts.append(e.verts[0].index) - - linked_verts = list(set(linked_verts) - set([ver_index])) - - return linked_verts - - -def qq_sort(bm, ver_index, vm, wm): - verts = link_verts(bm, ver_index) - pt0 = bm.verts[ver_index].co - - def ang_2d_sort(x): - pt1 = bm.verts[x].co - vec = vm * wm * pt1 - vm * wm * pt0 - vec = Vector(vec[0:2]) - ang = vec.angle_signed(Vector((1, 0))) - - if ang < 0: - return ang + 3.1415926 * 2 - else: - return ang - verts = sorted(verts, key=ang_2d_sort) - - return verts - - -def turn_left(bm, v1, v2, vm, wm): - links = [v1, v2] - - verts = qq_sort(bm, links[-1], vm, wm) - v = verts.index(links[-2]) - - if v == 0: - v_nxt = verts[-1] - else: - v_nxt = verts[v - 1] - - links.append(v_nxt) - - while not(links[-1] == links[1] and links[-2] == links[0]): - - verts = qq_sort(bm, links[-1], vm, wm) - v = verts.index(links[-2]) - if v == 0: - v_nxt = verts[-1] - else: - v_nxt = verts[v - 1] - - links.append(v_nxt) - links.pop() - - return links - - -def lp_left(bm, lp, wid, vm, wm): - size = len(lp) - up = wm.inverted() * vm.inverted() * Vector((0, 0, 1)) - lp_off = [] - faces = [] - for i in range(size - 1): - if i == 0: - pt = bm.verts[lp[i]].co - pre = bm.verts[lp[-2]].co - nxt = bm.verts[lp[1]].co - pre_ind = lp[size - 2] - nxt_ind = lp[1] - else: - bm.verts.ensure_lookup_table() - pt = bm.verts[lp[i]].co - pre = bm.verts[lp[i - 1]].co - nxt = bm.verts[lp[i + 1]].co - pre_ind = lp[i - 1] - nxt_ind = lp[i + 1] - - vec1 = pt - pre - vec2 = pt - nxt - - mid = vec1.normalized() + vec2.normalized() - if mid.length < 10e-4: - - up2 = Vector((0, 0, 1)) - mid = up2.cross(vec1) - - else: - xx = mid.cross(vec1).dot(up) - - if xx > 0: - mid.negate() - - mid.normalize() - if pre_ind == nxt_ind: - mid = (pt - pre).normalized() - q_a = Quaternion((0.0, 0.0, 1.0), radians(90.0)) - q_b = Quaternion((0.0, 0.0, 1.0), radians(-180.0)) - mid.rotate(q_a) - pt1 = pt + mid * wid - mid.rotate(q_b) - pt2 = pt + mid * wid - new_vert_1 = bm.verts.new(pt1) - new_vert_2 = bm.verts.new(pt2) - lp_off.append([new_vert_1, new_vert_2]) - else: - ang = mid.angle(pre - pt) - - vec_len = wid / (sin(ang)) - pt = pt + mid * vec_len - new_vert = bm.verts.new(pt) - lp_off.append(new_vert) - lp_off.append(lp_off[0]) - bm.verts.index_update() - for i in range(len(lp_off) - 1): - bm.verts.ensure_lookup_table() - p1 = bm.verts[lp[i]] - p2 = bm.verts[lp[i + 1]] - p3 = lp_off[i + 1] - p4 = lp_off[i] - - if isinstance(p3, list): - - faces.append((p1, p2, p3[0], p4)) - # faces.append((p3[0],p2,p3[1])) - elif isinstance(p4, list): - - faces.append((p1, p2, p3, p4[1])) - else: - faces.append((p1, p2, p3, p4)) - - return faces - - -# Operators - -class MeshtoWall(Operator): - bl_idname = "bpt.mesh_to_wall" - bl_label = "Edge(s) to Wall" - bl_description = "Top View, Extrude Flat Along Edges" - bl_options = {'REGISTER', 'UNDO'} - - wid = FloatProperty( - name='Wall width:', - default=0.1, - min=0.001, max=10 - ) - - def check_vert(self, context): - obj = bpy.context.object - if len(obj.data.vertices) <= 1: - return False - return True - - def execute(self, context): - # 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) # <<< Remove doubles is called from here - bpy.ops.object.mode_set(mode='OBJECT') - if self.check_vert(context): - bpy.ops.object.mode_set(mode='EDIT') - ob = bpy.context.object - - me = ob.data - bm = bmesh.from_edit_mesh(me) - bpy.ops.mesh.delete(type='ONLY_FACE') - - context.tool_settings.mesh_select_mode = (True, True, False) - - v3d = context.space_data - rv3d = v3d.region_3d - vm = rv3d.view_matrix - wm = ob.matrix_world - faces = [] - sel = [] - for v in bm.verts: - sel.append(v.index) - bpy.ops.mesh.select_all(action='DESELECT') - - for j in sel: - verts = link_verts(bm, j) - - if len(verts) > 1: - for i in verts: - lp = turn_left(bm, j, i, vm, wm) - - bpy.ops.mesh.select_all(action='DESELECT') - - faces += lp_left(bm, lp, self.wid * 0.5, vm, wm) - lp = [bm.verts[i] for i in lp] - - lp = lp[1:] - - bpy.ops.mesh.select_all(action='DESELECT') - for f in faces: - try: - bm.faces.new(f) - except: - pass - bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.003) - bm = bmesh.update_edit_mesh(ob.data, 1, 1) - else: - bpy.ops.object.mode_set(mode='EDIT') - self.report({'WARNING'}, "None or a single vertex found, cancelling the operation") - 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_vertex_chamfer.py b/mesh_extra_tools/mesh_vertex_chamfer.py index ba044663..d6fd8080 100644 --- a/mesh_extra_tools/mesh_vertex_chamfer.py +++ b/mesh_extra_tools/mesh_vertex_chamfer.py @@ -26,7 +26,6 @@ bl_info = { "location": "Spacebar Menu", "description": "Chamfer vertex", "wiki_url": "", - "tracker_url": "", "category": "Mesh"} @@ -133,9 +132,11 @@ class VertexChamfer(Operator): # 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) + bmesh.utils.face_split( + l.face, + l.link_loop_next.vert, + l.link_loop_prev.vert + ) # Remove the vert or displace otherwise if dissolve: @@ -149,11 +150,11 @@ class VertexChamfer(Operator): def register(): - bpy.utils.register_module(__name__) + bpy.utils.register_class(VertexChamfer) def unregister(): - bpy.utils.unregister_module(__name__) + bpy.utils.unregister_class(VertexChamfer) if __name__ == "__main__": diff --git a/mesh_extra_tools/pkhg_faces.py b/mesh_extra_tools/pkhg_faces.py index 36992e65..ec698e55 100644 --- a/mesh_extra_tools/pkhg_faces.py +++ b/mesh_extra_tools/pkhg_faces.py @@ -3,7 +3,7 @@ bl_info = { "name": "PKHG faces", "author": "PKHG", - "version": (0, 0, 5), + "version": (0, 0, 6), "blender": (2, 7, 1), "location": "View3D > Tools > PKHG (tab)", "description": "Faces selected will become added faces of different style", @@ -14,10 +14,7 @@ bl_info = { import bpy import bmesh -from bpy.types import ( - Operator, - Panel - ) +from bpy.types import Operator from mathutils import Vector from bpy.props import ( BoolProperty, @@ -35,159 +32,158 @@ class MESH_OT_add_faces_to_object(Operator): bl_options = {'REGISTER', 'UNDO', 'PRESET'} reverse_faces = BoolProperty( - name="Reverse Faces", - default=False, - description="Revert the normals of selected faces" - ) + 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" - ) + 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" - ) + 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" - ) + name="Base Height", + min=-20, + soft_max=10, max=20, + default=0.2, + description="Set general Base Height" + ) use_relative_base_height = BoolProperty( - name="rel.base_height", - default=False, - description="Relative or absolute Base Height" - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) + 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 - ) + 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 - ) + 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" - ) + 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" - ) + 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" - ) + 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" - ) - + 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" - ) + 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" - ) + 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" - ) + 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"), - ] - ) + 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" - ) + name="Strange Effect", + default=False, + description="Do not show one extrusion" + ) use_boundary = BoolProperty( - name="Use Boundary", - default=True - ) + name="Use Boundary", + default=True + ) use_even_offset = BoolProperty( - name="Even Offset", - default=True - ) + name="Even Offset", + default=True + ) use_relative_offset = BoolProperty( - name="Relative Offset", - default=True - ) + name="Relative Offset", + default=True + ) use_edge_rail = BoolProperty( - name="Edge Rail", - default=False - ) + name="Edge Rail", + default=False + ) use_outset = BoolProperty( - name="Outset", - default=False - ) + name="Outset", + default=False + ) use_select_inset = BoolProperty( - name="Inset", - default=False - ) + name="Inset", + default=False + ) use_interpolate = BoolProperty( - name="Interpolate", - default=True - ) + name="Interpolate", + default=True + ) @classmethod def poll(cls, context): @@ -438,21 +434,24 @@ class MESH_OT_add_faces_to_object(Operator): 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) - + 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) + 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() @@ -481,24 +480,30 @@ def find_one_ring(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): + 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) + 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') @@ -510,13 +515,16 @@ class Spiked: 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) + 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') @@ -596,50 +604,63 @@ class StripFaces: 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.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) + 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) + 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.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.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.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.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') @@ -668,16 +689,26 @@ def prepare(self, context, remove_start_faces=True): 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] + + 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] + + 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] @@ -692,20 +723,27 @@ def prepare(self, context, remove_start_faces=True): 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_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] + 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 + } - 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 @@ -769,8 +807,8 @@ def translate_ONE_ring(self, context, bm=None, object_matrix=None, ring_edges=No 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): +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: diff --git a/mesh_extra_tools/random_vertices.py b/mesh_extra_tools/random_vertices.py index 346f7abb..cf0ee224 100644 --- a/mesh_extra_tools/random_vertices.py +++ b/mesh_extra_tools/random_vertices.py @@ -6,10 +6,9 @@ bl_info = { "version": (1, 3), "blender": (2, 6, 3), "location": "Object > Transform > Random Vertices", - "description": "Randomize selected components of active object.", + "description": "Randomize selected components of active object", "warning": "", "wiki_url": "", - "tracker_url": "", "category": "Mesh"} @@ -45,7 +44,8 @@ def add_object(self, context, valmin, valmax, factor, vgfilter): if vertice.select: listver.append(vertice.index) - # If the minimum value is greater than the maximum, it adds a value to the maximum + # 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 @@ -95,25 +95,25 @@ class MESH_OT_random_vertices(Operator): bl_options = {'REGISTER', 'UNDO'} vgfilter = BoolProperty( - name="Vertex Group", - description="Use Vertex Weight defined in the Active Group", - default=False - ) + 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 - ) + 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) - ) + 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) - ) + name="Max XYZ", + description="Define the maximum range of randomization values", + default=(1, 1, 1) + ) @classmethod def poll(cls, context): @@ -122,6 +122,7 @@ class MESH_OT_random_vertices(Operator): def execute(self, context): add_object(self, context, self.valmin, self.valmax, self.factor, self.vgfilter) + return {'FINISHED'} diff --git a/mesh_extra_tools/split_solidify.py b/mesh_extra_tools/split_solidify.py index 147e096d..6b830543 100644 --- a/mesh_extra_tools/split_solidify.py +++ b/mesh_extra_tools/split_solidify.py @@ -1,22 +1,22 @@ # -*- coding: utf-8 -*- -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. +# 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. +# 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 LICENCE BLOCK ***** +# ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "Split Solidify", @@ -27,7 +27,6 @@ bl_info = { "description": "", "warning": "", "wiki_url": "", - "tracker_url": "", "category": "Mesh"} import bpy @@ -111,44 +110,44 @@ class MESH_OT_split_solidify(Operator): 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 - ) + 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 - ) + 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 - ) + 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 - ) + name="Random", + description="Randomize the locations of splitted faces", + default=False + ) del_original = BoolProperty( - name="Delete original faces", - default=True - ) + 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' - ) + items=(('opt0', "Face", "Solidify along Face Normals"), + ('opt1', "Vertex", "Solidify along Vertex Normals")), + name="Normal", + default='opt0' + ) def draw(self, context): layout = self.layout @@ -176,7 +175,8 @@ class MESH_OT_split_solidify(Operator): 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") + self.report({'WARNING'}, + "No suitable selection found. Operation cancelled") return {'CANCELLED'} diff --git a/mesh_extra_tools/vertex_align.py b/mesh_extra_tools/vertex_align.py index 8b3be88b..e5570fd1 100644 --- a/mesh_extra_tools/vertex_align.py +++ b/mesh_extra_tools/vertex_align.py @@ -1,63 +1,55 @@ # -*- coding: utf-8 -*- -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. # -# 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. # -# 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 LICENCE BLOCK ***** +# ##### END GPL LICENSE BLOCK ##### + +# Note: Property group was moved to __init__ -# ------ ------ bl_info = { - 'name': 'vertex align', - 'author': '', - 'version': (0, 1, 6), - 'blender': (2, 6, 1), - 'location': 'View3D > Tool Shelf', - 'description': '', - 'warning': '', - 'wiki_url': '', - 'tracker_url': '', - 'category': 'Mesh' } - -# ------ ------ + "name": "Vertex Align", + "author": "", + "version": (0, 1, 7), + "blender": (2, 6, 1), + "location": "View3D > Tool Shelf", + "description": "", + "warning": "", + "wiki_url": "", + "category": "Mesh"} + + import bpy from bpy.props import ( - BoolProperty, - EnumProperty, - PointerProperty, - FloatProperty, + BoolVectorProperty, + FloatVectorProperty, ) - from mathutils import Vector -from mathutils.geometry import ( - intersect_point_line, - intersect_line_plane, - ) -from bpy.types import ( - PropertyGroup, - ) +from bpy.types import Operator -# ------ Edit Mode Toggle------ + +# Edit Mode Toggle def edit_mode_out(): - bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') + def edit_mode_in(): - bpy.ops.object.mode_set(mode = 'EDIT') + bpy.ops.object.mode_set(mode='EDIT') + -# ------ ------ def get_mesh_data_(): edit_mode_out() ob_act = bpy.context.active_object @@ -65,160 +57,206 @@ def get_mesh_data_(): edit_mode_in() return me + def list_clear_(l): l[:] = [] return l -# -- -- Prpoerty Group-- -- -class va_property_group(PropertyGroup): - - en0 = EnumProperty( items =( ('vertex', 'Original vertex', ''), - ('coordinates', 'Custom coordinates', '')), - - name = 'Align to', - default = 'vertex' ) - - en1 = EnumProperty( items =( ('en1_opt0', 'x', ''), - ('en1_opt1', 'y', ''), - ('en1_opt2', 'z', '')), - name = 'Axis', - default = 'en1_opt0' ) -# ------ ------ class va_buf(): list_v = [] - list_f = [] list_0 = [] -# ------ operator 0 Store The Vert------ -class va_op0_store(bpy.types.Operator): - bl_idname = 'va.op0_store_id' - bl_label = '' - bl_description = "Store single vert as align point" - def execute(self, context): - me = get_mesh_data_() - 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') - return {'FINISHED'} +# 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") -# ------ operator 1 Append to list------ -class va_op1_list(bpy.types.Operator): - bl_idname = 'va.op1_id' - bl_label = '' - bl_description = "test2" + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH') def execute(self, context): - me = get_mesh_data_() - list_clear_(va_buf.list_f) - for f in me.faces: - if f.select: - va_buf.list_f.append(f.index) - bpy.ops.mesh.select_all(action = 'DESELECT') + 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'} -# ------ operator 2 ------ align to original -class va_op2_align(bpy.types.Operator): - bl_idname = 'va.op2_align_id' - bl_label = 'Align to original' + +# 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'} - bl_description = "Align selection to stored single vert" + @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('Axis:') - layout.prop(context.scene.va_custom_props, 'en1', expand = True) + layout.label("Axis:") - def execute(self, context): + 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.va_custom_props.en1 + 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') + 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') + 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 == 'en1_opt0': + 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] )) - elif cen1 == 'en1_opt1': + 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] )) - elif cen1 == 'en1_opt2': + 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] )) + me.vertices[i].co = Vector((v[0], v[1], vo[2])) edit_mode_in() + return {'FINISHED'} -# ------ operator 3 ------ align to custom coordinates -class va_op3_coord_list(bpy.types.Operator): - bl_idname = 'va.op3_coord_list_id' - bl_label = '' + +# 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') + self.report({'INFO'}, "No vertices selected. Operation Cancelled") edit_mode_in() return {'CANCELLED'} + elif len(va_buf.list_0) != 0: - bpy.ops.va.op4_id('INVOKE_DEFAULT') + bpy.ops.vertex_align.coord_menu_id('INVOKE_DEFAULT') + edit_mode_in() + return {'FINISHED'} -# ------ operator 4 ------ align to custom coordinates menu -class va_op4_coord_menu(bpy.types.Operator): - bl_idname = 'va.op4_id' - bl_label = 'Align to custom coordinates' + +# 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'} - bl_description = "Align to custom coordinates2" - x = y = z = FloatProperty( name = '', - default = 0.0, - min = -100.0, - max = 100.0, - step = 1, - precision = 3 ) - b_x = b_y = b_z = BoolProperty() + 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("Using Stored Coordinates", icon="INFO") + row = layout.split(0.25) - row.prop(self, 'b_x', text = 'x') - row.prop(self, 'x') + 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, 'b_y', text = 'y') - row.prop(self, 'y') + 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, 'b_z', text = 'z') - row.prop(self, 'z') + row.prop(self, "use_axis_coord", index=2, text="Z") + row.prop(self, "def_axis_coord", index=2) def invoke(self, context, event): - return context.window_manager.invoke_props_dialog(self, width = 200) + 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 @@ -226,71 +264,38 @@ class va_op4_coord_menu(bpy.types.Operator): for i in va_buf.list_0: v = (me.vertices[i].co).copy() tmp = Vector((v[0], v[1], v[2])) - if self.b_x == True: - tmp[0] = self.x - if self.b_y == True: - tmp[1] = self.y - if self.b_z == True: - tmp[2] = self.z - me.vertices[i].co = tmp - edit_mode_in() - return {'FINISHED'} -# ------ operator 7 Help------ -class va_op7_help(bpy.types.Operator): - - bl_idname = 'va.op7_help_id' - bl_label = '' - bl_description = "Info" - - def draw(self, context): - layout = self.layout - layout.label('Help:') - layout.label('To use select whatever you want vertices to be aligned to ') - layout.label('and click button next to store data label. ') - layout.label('Select vertices that you want to align and click Align button. ') - - def execute(self, context): - return {'FINISHED'} + 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 - def invoke(self, context, event): - return context.window_manager.invoke_popup(self, width = 400) + edit_mode_in() -# ------ operator 8 ------ -class va_op8_execute(bpy.types.Operator): - bl_idname = 'va.op8_id' - bl_label = '' - bl_description = "Executs" - def execute(self, context): - bpy.ops.va.op7_id('INVOKE_DEFAULT') return {'FINISHED'} -# ------ Classes ------ +# Register classes = ( - va_property_group, - va_op0_store, - va_op1_list, - va_op2_align, - va_op3_coord_list, - va_op4_coord_menu, - va_op7_help, - va_op8_execute, + Vertex_align_store, + Vertex_align_original, + Vertex_align_coord_list, + Vertex_align_coord_menu, ) -# ------ Register ------ + + def register(): - for c in classes: - bpy.utils.register_class(c) + for cls in classes: + bpy.utils.register_class(cls) - bpy.types.Scene.va_custom_props = PointerProperty(type = va_property_group) -# ------ ------ def unregister(): - del bpy.types.Scene.va_custom_props + for cls in classes: + bpy.utils.unregister_class(cls) - for c in classes: - bpy.utils.unregister_class(c) -# ------ ------ if __name__ == "__main__": register() diff --git a/mesh_extra_tools/vfe_specials.py b/mesh_extra_tools/vfe_specials.py index da451e66..714f1b3e 100644 --- a/mesh_extra_tools/vfe_specials.py +++ b/mesh_extra_tools/vfe_specials.py @@ -11,7 +11,6 @@ bl_info = { import bpy import bpy_extras - from bpy.types import ( Menu, Operator, @@ -60,16 +59,17 @@ class MESH_OT_CallContextMenu(Operator): return bpy.ops.wm.call_menu(name=MESH_MT_CombinedMenu.bl_idname) -classes = [ +classes = ( MESH_MT_CombinedMenu, - MESH_OT_CallContextMenu -] + 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) + # 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'}, ()), |