# ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### # bl_info = { "name": "Inset Polygon", "author": "Howard Trickey", "version": (0, 3), "blender": (2, 5, 7), "api": 36147, "location": "View3D > Tools", "description": "Make an inset polygon inside selection.", "warning": "", "wiki_url": \ "http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/Modeling/Inset-Polygon", "tracker_url": \ "http://projects.blender.org/tracker/index.php?func=detail&aid=27290&group_id=153&atid=468", "category": "Mesh"} if "bpy" in locals(): import imp else: from . import geom from . import model from . import offset from . import triquad import math import bpy import mathutils from bpy.props import * class Inset(bpy.types.Operator): bl_idname = "mesh.inset" bl_label = "Inset" bl_description = "Make an inset polygon inside selection" bl_options = {'REGISTER', 'UNDO'} inset_amount = FloatProperty(name="Amount", description="Amount to move inset edges", default=5.0, min=0.0, max=1000.0, soft_min=0.0, soft_max=100.0, unit='LENGTH') inset_height = FloatProperty(name="Height", description="Amount to raise inset faces", default=0.0, min=-10000.0, max=10000.0, soft_min=-500.0, soft_max=500.0, unit='LENGTH') region = BoolProperty(name="Region", description="Inset selection as one region?", default=True) scale = EnumProperty(name="Scale", description="Scale for amount", items=[ ('PERCENT', "Percent", "Percentage of maximum inset amount"), ('ABSOLUTE', "Absolute", "Length in blender units") ], default='PERCENT') @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 box = layout.box() box.label("Inset Options") box.prop(self, "scale") box.prop(self, "inset_amount") box.prop(self, "inset_height") box.prop(self, "region") def invoke(self, context, event): self.action(context) return {'FINISHED'} def execute(self, context): self.action(context) return {'FINISHED'} def action(self, context): save_global_undo = bpy.context.user_preferences.edit.use_global_undo bpy.context.user_preferences.edit.use_global_undo = False bpy.ops.object.mode_set(mode='OBJECT') obj = bpy.context.active_object mesh = obj.data do_inset(mesh, self.inset_amount, self.inset_height, self.region, self.scale == 'PERCENT') bpy.ops.object.mode_set(mode='EDIT') bpy.context.user_preferences.edit.use_global_undo = save_global_undo def do_inset(mesh, amount, height, region, as_percent): if amount <= 0.0: return pitch = math.atan(height / amount) selfaces = [] selface_indices = [] for face in mesh.faces: if face.select and not face.hide: selfaces.append(face) selface_indices.append(face.index) m = geom.Model() # if add all mesh.vertices, coord indices will line up # Note: not using Points.AddPoint which does dup elim # because then would have to map vertices in and out m.points.pos = [v.co.to_tuple() for v in mesh.vertices] for f in selfaces: m.faces.append(list(f.vertices)) m.face_data.append(f.index) orig_numv = len(m.points.pos) orig_numf = len(m.faces) model.BevelSelectionInModel(m, amount, pitch, True, region, as_percent) if len(m.faces) == orig_numf: # something went wrong with Bevel - just treat as no-op return # blender_faces: newfaces but all 4-tuples and no 0 # in 4th position if a 4-sided poly blender_faces = [] blender_old_face_index = [] for i in range(orig_numf, len(m.faces)): f = m.faces[i] if len(f) == 3: blender_faces.append(list(f) + [0]) blender_old_face_index.append(m.face_data[i]) elif len(f) == 4: if f[3] == 0: blender_faces.append([f[3], f[0], f[1], f[2]]) else: blender_faces.append(f) blender_old_face_index.append(m.face_data[i]) num_new_vertices = len(m.points.pos) - orig_numv mesh.vertices.add(num_new_vertices) for i in range(orig_numv, len(m.points.pos)): mesh.vertices[i].co = mathutils.Vector(m.points.pos[i]) start_faces = len(mesh.faces) mesh.faces.add(len(blender_faces)) for i, newf in enumerate(blender_faces): mesh.faces[start_faces + i].vertices_raw = newf # copy face attributes from old face that it was derived from bfi = blender_old_face_index[i] if bfi and 0 <= bfi < start_faces: bfacenew = mesh.faces[start_faces + i] bface = mesh.faces[bfi] bfacenew.material_index = bface.material_index bfacenew.use_smooth = bface.use_smooth mesh.update(calc_edges=True) # remove original faces bpy.ops.object.mode_set(mode='EDIT') save_select_mode = bpy.context.tool_settings.mesh_select_mode bpy.context.tool_settings.mesh_select_mode = [False, False, True] bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.object.mode_set(mode='OBJECT') for fi in selface_indices: mesh.faces[fi].select = True bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.delete(type='FACE') bpy.context.tool_settings.mesh_select_mode = save_select_mode def panel_func(self, context): self.layout.label(text="Inset:") self.layout.operator("mesh.inset", text="Inset") def register(): bpy.utils.register_class(Inset) bpy.types.VIEW3D_PT_tools_meshedit.append(panel_func) def unregister(): bpy.utils.unregister_class(Inset) bpy.types.VIEW3D_PT_tools_meshedit.remove(panel_func) if __name__ == "__main__": register()