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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHoward Trickey <howard.trickey@gmail.com>2019-02-19 04:50:21 +0300
committerHoward Trickey <howard.trickey@gmail.com>2019-02-19 04:55:35 +0300
commit0b2d51126c5fbeebb313ebd791fbbbf79b927f8d (patch)
tree0c87bf636e0e342c1c00c079dd1915b76a53f188 /mesh_inset
parent0921d493d620e27d47f4869e6a2e238f0c7cf8e4 (diff)
Update mesh_inset (alternate inset using straight skeleton) for 2.8.
Update also made it modal, with interaction mode as in built-in inset, where mouse movement adjusts amount and holding control makes mouse movement affext height. Also renamed from "Inset Polygon" to "Inset Straight Skeleton" to lessen confusion with built-in one and emphasizing why this one is different. Recommend binding mesh.insetstraightskeleton to a key, like Alt-i
Diffstat (limited to 'mesh_inset')
-rw-r--r--mesh_inset/__init__.py225
1 files changed, 175 insertions, 50 deletions
diff --git a/mesh_inset/__init__.py b/mesh_inset/__init__.py
index e7c7d587..781b6962 100644
--- a/mesh_inset/__init__.py
+++ b/mesh_inset/__init__.py
@@ -19,12 +19,12 @@
# <pep8 compliant>
bl_info = {
- "name": "Inset Polygon",
+ "name": "Inset Straight Skeleton",
"author": "Howard Trickey",
- "version": (1, 0, 1),
- "blender": (2, 73, 0),
- "location": "View3D > Tools",
- "description": "Make an inset polygon inside selection.",
+ "version": (1, 1),
+ "blender": (2, 80, 0),
+ "location": "3DView Operator",
+ "description": "Make an inset inside selection using straight skeleton algorithm.",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Modeling/Inset-Polygon",
@@ -45,22 +45,35 @@ import math
import bpy
import bmesh
import mathutils
+from mathutils import Vector
+from bpy_extras import view3d_utils
+import gpu
+from gpu_extras.batch import batch_for_shader
+
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
)
+SpaceView3D = bpy.types.SpaceView3D
+
+INSET_VALUE = 0
+HEIGHT_VALUE = 1
+NUM_VALUES = 2
+
+# TODO: make a dooted-line shader
+shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
-class Inset(bpy.types.Operator):
- bl_idname = "mesh.insetpoly"
- bl_label = "Inset Polygon"
- bl_description = "Make an inset polygon inside selection"
- bl_options = {'REGISTER', 'UNDO'}
+class MESH_OT_InsetStraightSkeleton(bpy.types.Operator):
+ bl_idname = "mesh.insetstraightskeleton"
+ bl_label = "Inset Straight Skeleton"
+ bl_description = "Make an inset inside selection using straight skeleton algorithm"
+ bl_options = {'UNDO', 'REGISTER', 'GRAB_CURSOR', 'BLOCKING'}
inset_amount: FloatProperty(name="Amount",
description="Amount to move inset edges",
- default=5.0,
+ default=0.0,
min=0.0,
max=1000.0,
soft_min=0.0,
@@ -77,15 +90,9 @@ class Inset(bpy.types.Operator):
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')
+ quadrangulate: BoolProperty(name="Quadrangulate",
+ description="Quadrangulate after inset?",
+ default=True)
@classmethod
def poll(cls, context):
@@ -96,32 +103,152 @@ class Inset(bpy.types.Operator):
layout = self.layout
box = layout.box()
box.label(text="Inset Options:")
- box.prop(self, "scale")
box.prop(self, "inset_amount")
box.prop(self, "inset_height")
box.prop(self, "region")
+ box.prop(self, "quadrangulate")
def invoke(self, context, event):
+ self.modal = True
+ # make backup bmesh from current mesh, after flushing editmode to mesh
+ bpy.context.object.update_from_editmode()
+ self.backup = bmesh.new()
+ self.backup.from_mesh(bpy.context.object.data)
+ self.inset_amount = 0.0
+ self.inset_height = 0.0
+ self.center, self.center3d = calc_select_center(context)
+ self.center_pixel_size = calc_pixel_size(context, self.center3d)
+ udpi = context.preferences.system.dpi
+ upixelsize = context.preferences.system.pixel_size
+ self.pixels_per_inch = udpi * upixelsize
+ self.value_mode = INSET_VALUE
+ self.initial_length = [-1.0, -1.0]
+ self.scale = [self.center_pixel_size] * NUM_VALUES
+ self.calc_initial_length(event, True)
+ self.mouse_cur = Vector((event.mouse_region_x, event.mouse_region_y))
+ col = context.preferences.themes["Default"].view_3d.view_overlay
+ self.line_color = (col.r, col.g, col.b, 1.0)
+
self.action(context)
- return {'FINISHED'}
+
+ context.window_manager.modal_handler_add(self)
+ self.draw_handle = SpaceView3D.draw_handler_add(draw_callback,
+ (self,), 'WINDOW', 'POST_PIXEL')
+
+ return {'RUNNING_MODAL'}
+
+ def calc_initial_length(self, event, mode_changed):
+ mdiff = self.center - Vector((event.mouse_region_x, event.mouse_region_y))
+ mlen = mdiff.length;
+ vmode = self.value_mode
+ if mode_changed or self.initial_length[vmode] == -1:
+ if vmode == INSET_VALUE:
+ value = self.inset_amount
+ else:
+ value = self.inset_height
+ sc = self.scale[vmode]
+ if value != 0.0:
+ mlen = mlen - value / sc
+ self.initial_length[vmode] = mlen
+
+ def modal(self, context, event):
+ if event.type in ['LEFTMOUSE', 'RIGHTMOUSE', 'ESC']:
+ if self.modal:
+ self.backup.free()
+ if self.draw_handle:
+ SpaceView3D.draw_handler_remove(self.draw_handle, 'WINDOW')
+ context.area.tag_redraw()
+ if event.type == 'LEFTMOUSE': # Confirm
+ return {'FINISHED'}
+ else: # Cancel
+ return {'CANCELLED'}
+ else:
+ # restore mesh to original state
+ bpy.ops.object.editmode_toggle()
+ self.backup.to_mesh(bpy.context.object.data)
+ bpy.ops.object.editmode_toggle()
+ if event.type == 'MOUSEMOVE':
+ if self.value_mode == INSET_VALUE and event.ctrl:
+ self.value_mode = HEIGHT_VALUE
+ self.calc_initial_length(event, True)
+ elif self.value_mode == HEIGHT_VALUE and not event.ctrl:
+ self.value_mode = INSET_VALUE
+ self.calc_initial_length(event, True)
+ self.mouse_cur = Vector((event.mouse_region_x, event.mouse_region_y))
+ vmode = self.value_mode
+ mdiff = self.center - self.mouse_cur
+ value = (mdiff.length - self.initial_length[vmode]) * self.scale[vmode]
+ if vmode == INSET_VALUE:
+ self.inset_amount = value
+ else:
+ self.inset_height = value
+ elif event.type == 'R' and event.value == 'PRESS':
+ self.region = not self.region
+ elif event.type == 'Q' and event.value == 'PRESS':
+ self.quadrangulate = not self.quadrangulate
+ self.action(context)
+
+ return {'RUNNING_MODAL'}
def execute(self, context):
+ self.modal = False
self.action(context)
return {'FINISHED'}
def action(self, context):
- save_global_undo = bpy.context.preferences.edit.use_global_undo
- bpy.context.preferences.edit.use_global_undo = False
obj = bpy.context.active_object
mesh = obj.data
do_inset(mesh, self.inset_amount, self.inset_height, self.region,
- self.scale == 'PERCENT')
- bpy.context.preferences.edit.use_global_undo = save_global_undo
+ self.quadrangulate)
bpy.ops.object.editmode_toggle()
bpy.ops.object.editmode_toggle()
-def do_inset(mesh, amount, height, region, as_percent):
+def draw_callback(op):
+ startpos = op.mouse_cur
+ endpos = op.center
+ coords = [startpos.to_tuple(), endpos.to_tuple()]
+ batch = batch_for_shader(shader, 'LINES', {"pos": coords})
+
+ try:
+ shader.bind()
+ shader.uniform_float("color", op.line_color)
+ batch.draw(shader)
+ except:
+ pass
+
+def calc_pixel_size(context, co):
+ # returns size in blender units of a pixel at 3d coord co
+ # see C code in ED_view3d_pixel_size and ED_view3d_update_viewmat
+ m = context.region_data.perspective_matrix
+ v1 = m[0].to_3d()
+ v2 = m[1].to_3d()
+ ll = min(v1.length_squared, v2.length_squared)
+ len_pz = 2.0 / math.sqrt(ll)
+ len_sz = max(context.region.width, context.region.height)
+ rv3dpixsize = len_pz / len_sz
+ proj = m[3][0] * co[0] + m[3][1] * co[1] + m[3][2] * co[2] + m[3][3]
+ ups = context.preferences.system.pixel_size
+ return proj * rv3dpixsize * ups
+
+def calc_select_center(context):
+ # returns region 2d coord and global 3d coord of selection center
+ ob = bpy.context.active_object
+ mesh = ob.data
+ center = Vector((0.0, 0.0, 0.0))
+ n = 0
+ for v in mesh.vertices:
+ if v.select:
+ center = center + Vector(v.co)
+ n += 1
+ if n > 0:
+ center = center / n
+ world_center = ob.matrix_world @ center
+ world_center_2d = view3d_utils.location_3d_to_region_2d( \
+ context.region, context.region_data, world_center)
+ return (world_center_2d, world_center)
+
+def do_inset(mesh, amount, height, region, quadrangulate):
if amount <= 0.0:
return
pitch = math.atan(height / amount)
@@ -142,7 +269,7 @@ def do_inset(mesh, amount, height, region, as_percent):
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)
+ model.BevelSelectionInModel(m, amount, pitch, quadrangulate, region, False)
if len(m.faces) == orig_numf:
# something went wrong with Bevel - just treat as no-op
return
@@ -160,41 +287,39 @@ def do_inset(mesh, amount, height, region, as_percent):
continue
# copy face attributes from old face that it was derived from
bfi = blender_old_face_index[i]
- if bfi and 0 <= bfi < start_faces:
- bm.faces.ensure_lookup_table()
- oldface = bm.faces[bfi]
- bfacenew = bm.faces.new(vs, oldface)
- # bfacenew.copy_from_face_interp(oldface)
- else:
- bfacenew = bm.faces.new(vs)
- new_faces.append(bfacenew)
+ # sometimes, not sure why, this face already exists
+ # bmesh will give a value error in bm.faces.new() in that case
+ try:
+ if bfi and 0 <= bfi < start_faces:
+ bm.faces.ensure_lookup_table()
+ oldface = bm.faces[bfi]
+ bfacenew = bm.faces.new(vs, oldface)
+ # bfacenew.copy_from_face_interp(oldface)
+ else:
+ bfacenew = bm.faces.new(vs)
+ new_faces.append(bfacenew)
+ except ValueError:
+ # print("dup face with amount", amount)
+
+ # print([v.index for v in vs])
+ pass
# deselect original faces
for face in selfaces:
face.select_set(False)
# remove original faces
- bmesh.ops.delete(bm, geom=selfaces, context=5) # 5 = DEL_FACES
+ bmesh.ops.delete(bm, geom=selfaces, context='FACES')
# select all new faces (should only select inner faces, but that needs more surgery on rest of code)
for face in new_faces:
face.select_set(True)
+ bmesh.update_edit_mesh(mesh)
def remove_dups(vs):
seen = set()
return [x for x in vs if not (x in seen or seen.add(x))]
-def panel_func(self, context):
- self.layout.label(text="Inset Polygon:")
- self.layout.operator("mesh.insetpoly", text="Inset Polygon")
-
def register():
- bpy.utils.register_class(Inset)
- bpy.types.VIEW3D_PT_tools_meshedit.append(panel_func)
-
+ bpy.utils.register_class(MESH_OT_InsetStraightSkeleton)
def unregister():
- bpy.utils.unregister_class(Inset)
- bpy.types.VIEW3D_PT_tools_meshedit.remove(panel_func)
-
-
-if __name__ == "__main__":
- register()
+ bpy.utils.unregister_class(MESH_OT_InsetStraightSkeleton)