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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2017-03-20 02:33:38 +0300
committermeta-androcto <meta.androcto1@gmail.com>2017-03-20 02:33:38 +0300
commit8e3bfa5506ea110fe6793401b53da17c61061167 (patch)
tree8e53f830a72b434928b07b99befe8762431a4ed3 /mesh_extra_tools/mesh_pen_tool.py
parent9007bcd10713e55168235e9e8420b17172674638 (diff)
initial commit mesh edit tools: T50680
Diffstat (limited to 'mesh_extra_tools/mesh_pen_tool.py')
-rw-r--r--mesh_extra_tools/mesh_pen_tool.py553
1 files changed, 553 insertions, 0 deletions
diff --git a/mesh_extra_tools/mesh_pen_tool.py b/mesh_extra_tools/mesh_pen_tool.py
new file mode 100644
index 00000000..ebe5ae8d
--- /dev/null
+++ b/mesh_extra_tools/mesh_pen_tool.py
@@ -0,0 +1,553 @@
+# -*- coding: utf-8 -*-
+
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_info = {
+ "name": "Pen Tool",
+ "author": "zmj100",
+ "version": (0, 2, 8),
+ "blender": (2, 6, 5),
+ "location": "View3D > Tool Shelf",
+ "description": "",
+ "warning": "",
+ "wiki_url": "",
+ "tracker_url": "",
+ "category": "Mesh",
+ }
+
+import bpy
+import blf
+import bgl
+import bmesh
+from bpy.types import (
+ Operator,
+ PropertyGroup,
+ Panel
+ )
+from bpy.props import (
+ FloatProperty,
+ IntProperty,
+ PointerProperty,
+ BoolProperty
+ )
+from bpy_extras.view3d_utils import region_2d_to_location_3d, location_3d_to_region_2d
+from mathutils import Vector, Matrix
+from math import degrees
+
+
+def edit_mode_out():
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+
+def edit_mode_in():
+ bpy.ops.object.mode_set(mode='EDIT')
+
+
+def get_direction_(bme, list_, ob_act):
+ n = len(list_)
+ for i in range(n):
+ p = ob_act.matrix_world * (bme.verts[list_[i]].co).copy()
+ p1 = ob_act.matrix_world * (bme.verts[list_[(i - 1) % n]].co).copy()
+ p2 = ob_act.matrix_world * (bme.verts[list_[(i + 1) % n]].co).copy()
+
+ if p == p1 or p == p2:
+ continue
+ ang = round(degrees((p - p1).angle((p - p2), any)))
+ if ang == 0 or ang == 180:
+ continue
+ elif ang != 0 or ang != 180:
+ return(((p - p1).cross((p - p2))).normalized())
+ break
+
+
+def store_restore_view(context, store=True):
+ if not context.scene.pen_tool_props.restore_view:
+ return
+
+ if store is True:
+ # copy the original view_matrix and rotation for restoring
+ pt_buf.store_view_matrix = context.space_data.region_3d.view_matrix.copy()
+ pt_buf.view_location = context.space_data.region_3d.view_location.copy()
+ else:
+ context.space_data.region_3d.view_matrix = pt_buf.store_view_matrix
+ context.space_data.region_3d.view_location = pt_buf.view_location
+
+
+def align_view_to_face_(context, bme, f):
+ store_restore_view(context, True)
+ ob_act = context.active_object
+ list_e = [[v.index for v in e.verts] for e in f.edges][0]
+ vec0 = -get_direction_(bme, [v.index for v in f.verts], ob_act)
+ vec1 = ((ob_act.matrix_world * bme.verts[list_e[0]].co.copy()) -
+ (ob_act.matrix_world * bme.verts[list_e[1]].co.copy())).normalized()
+ vec2 = (vec0.cross(vec1)).normalized()
+ context.space_data.region_3d.view_matrix = ((Matrix((vec1, vec2, vec0))).to_4x4()).inverted()
+ context.space_data.region_3d.view_location = f.calc_center_median()
+
+
+def draw_callback_px(self, context):
+ font_id = 0
+ alpha = context.scene.pen_tool_props.a
+ font_size = context.scene.pen_tool_props.fs
+
+ bgl.glColor4f(0.0, 0.6, 1.0, alpha)
+ bgl.glPointSize(4.0)
+ bgl.glBegin(bgl.GL_POINTS)
+ bgl.glVertex2f(pt_buf.x, pt_buf.y)
+ bgl.glEnd()
+ bgl.glDisable(bgl.GL_BLEND)
+
+ # location 3d
+ if context.scene.pen_tool_props.b2 is True:
+ mloc3d = region_2d_to_location_3d(
+ context.region,
+ context.space_data.region_3d, Vector((pt_buf.x, pt_buf.y)),
+ pt_buf.depth_location
+ )
+ blf.position(font_id, pt_buf.x + 15, pt_buf.y - 15, 0)
+ blf.size(font_id, font_size, context.user_preferences.system.dpi)
+ blf.draw(font_id,
+ '(' + str(round(mloc3d[0], 4)) + ', ' + str(round(mloc3d[1], 4)) +
+ ', ' + str(round(mloc3d[2], 4)) + ')')
+
+ n = len(pt_buf.list_m_loc_3d)
+
+ if n != 0:
+ # add points
+ bgl.glEnable(bgl.GL_BLEND)
+ bgl.glPointSize(4.0)
+ bgl.glBegin(bgl.GL_POINTS)
+ for i in pt_buf.list_m_loc_3d:
+ loc_0 = location_3d_to_region_2d(context.region, context.space_data.region_3d, i)
+ bgl.glVertex2f(loc_0[0], loc_0[1])
+ bgl.glEnd()
+ bgl.glDisable(bgl.GL_BLEND)
+
+ # text next to the mouse
+ m_loc_3d = region_2d_to_location_3d(
+ context.region,
+ context.space_data.region_3d, Vector((pt_buf.x, pt_buf.y)),
+ pt_buf.depth_location
+ )
+ vec0 = pt_buf.list_m_loc_3d[-1] - m_loc_3d
+ blf.position(font_id, pt_buf.x + 15, pt_buf.y + 15, 0)
+ blf.size(font_id, font_size, context.user_preferences.system.dpi)
+ blf.draw(font_id, str(round(vec0.length, 4)))
+
+ # angle first after mouse
+ if n >= 2:
+ vec1 = pt_buf.list_m_loc_3d[-2] - pt_buf.list_m_loc_3d[-1]
+ if vec0.length == 0.0 or vec1.length == 0.0:
+ pass
+ else:
+ ang = vec0.angle(vec1)
+
+ if round(degrees(ang), 2) == 180.0:
+ text_0 = '0.0'
+ elif round(degrees(ang), 2) == 0.0:
+ text_0 = '180.0'
+ else:
+ text_0 = str(round(degrees(ang), 2))
+
+ loc_4 = location_3d_to_region_2d(
+ context.region,
+ context.space_data.region_3d,
+ pt_buf.list_m_loc_3d[-1]
+ )
+ bgl.glColor4f(0.0, 1.0, 0.525, alpha)
+ blf.position(font_id, loc_4[0] + 10, loc_4[1] + 10, 0)
+ blf.size(font_id, font_size, context.user_preferences.system.dpi)
+ blf.draw(font_id, text_0 + '')
+
+ bgl.glLineStipple(4, 0x5555)
+ bgl.glEnable(bgl.GL_LINE_STIPPLE) # enable line stipple
+
+ bgl.glColor4f(0.0, 0.6, 1.0, alpha)
+ # draw line between last point and mouse
+ bgl.glEnable(bgl.GL_BLEND)
+ bgl.glBegin(bgl.GL_LINES)
+ loc_1 = location_3d_to_region_2d(
+ context.region,
+ context.space_data.region_3d,
+ pt_buf.list_m_loc_3d[-1]
+ )
+ bgl.glVertex2f(loc_1[0], loc_1[1])
+ bgl.glVertex2f(pt_buf.x, pt_buf.y)
+ bgl.glEnd()
+ bgl.glDisable(bgl.GL_BLEND)
+
+ # draw lines between points
+ bgl.glEnable(bgl.GL_BLEND)
+ bgl.glBegin(bgl.GL_LINE_STRIP)
+ for j in pt_buf.list_m_loc_3d:
+ loc_2 = location_3d_to_region_2d(context.region, context.space_data.region_3d, j)
+ bgl.glVertex2f(loc_2[0], loc_2[1])
+ bgl.glEnd()
+ bgl.glDisable(bgl.GL_BLEND)
+
+ bgl.glDisable(bgl.GL_LINE_STIPPLE) # disable line stipple
+
+ # draw line length between points
+ if context.scene.pen_tool_props.b1 is True:
+ for k in range(n - 1):
+ loc_3 = location_3d_to_region_2d(
+ context.region, context.space_data.region_3d,
+ (pt_buf.list_m_loc_3d[k] + pt_buf.list_m_loc_3d[(k + 1) % n]) * 0.5
+ )
+ blf.position(font_id, loc_3[0] + 10, loc_3[1] + 10, 0)
+ blf.size(font_id, font_size, context.user_preferences.system.dpi)
+ blf.draw(font_id,
+ str(round((pt_buf.list_m_loc_3d[k] - pt_buf.list_m_loc_3d[(k + 1) % n]).length, 4)))
+
+ # draw all angles
+ if context.scene.pen_tool_props.b0 is True:
+ for h in range(n - 1):
+ if n >= 2:
+ if h == 0:
+ pass
+ else:
+ vec_ = pt_buf.list_m_loc_3d[h] - pt_buf.list_m_loc_3d[(h - 1) % n]
+ vec_1_ = pt_buf.list_m_loc_3d[h]
+ vec_2_ = pt_buf.list_m_loc_3d[(h - 1) % n]
+ if vec_.length == 0.0 or vec_1_.length == 0.0 or vec_2_.length == 0.0:
+ pass
+ else:
+ ang = vec_.angle(vec_1_ - vec_2_)
+ if round(degrees(ang)) == 0.0:
+ pass
+ else:
+ loc_4 = location_3d_to_region_2d(
+ context.region, context.space_data.region_3d,
+ pt_buf.list_m_loc_3d[h]
+ )
+ bgl.glColor4f(0.0, 1.0, 0.525, alpha)
+ blf.position(font_id, loc_4[0] + 10, loc_4[1] + 10, 0)
+ blf.size(font_id, font_size, context.user_preferences.system.dpi)
+ blf.draw(font_id, str(round(degrees(ang), 2)) + '')
+ # tools on / off
+ bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
+ blf.position(font_id, self.text_location, 20, 0)
+ blf.size(font_id, 15, context.user_preferences.system.dpi)
+ blf.draw(font_id, "Draw On")
+ blf.position(font_id, self.text_location, 40, 0)
+ blf.draw(font_id, "Extrude On" if pt_buf.ctrl else "Extrude Off")
+
+
+class pen_tool_properties(PropertyGroup):
+ a = FloatProperty(
+ name="Alpha",
+ description="Set Font Alpha",
+ default=1.0,
+ min=0.1, max=1.0,
+ step=10,
+ precision=1
+ )
+ fs = IntProperty(
+ name="Size",
+ description="Set Font Size",
+ default=14,
+ min=12, max=40,
+ step=1
+ )
+ b0 = BoolProperty(
+ name="Angles",
+ description="Display All Angles on Drawn Edges",
+ default=False
+ )
+ b1 = BoolProperty(
+ name="Edge Length",
+ description="Display All Lenghts of Drawn Edges",
+ default=False
+ )
+ b2 = BoolProperty(
+ name="Mouse Location 3D",
+ description="Display the location coordinates of the mouse cursor",
+ default=False
+ )
+ restore_view = BoolProperty(
+ name="Restore View",
+ description="After the tool has finished, is the Viewport restored\n"
+ "to it's previous state",
+ default=True
+ )
+
+
+class pt_buf():
+ list_m_loc_2d = []
+ list_m_loc_3d = []
+ x = 0
+ y = 0
+ sws = 'off'
+ depth_location = Vector((0.0, 0.0, 0.0))
+ alt = False
+ shift = False
+ ctrl = False
+ store_view_matrix = Matrix()
+ view_location = (0.0, 0.0, 0.0)
+
+
+# ------ Panel ------
+class pen_tool_panel(Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_category = "Tools"
+ bl_label = "Pen Tool"
+ bl_context = "mesh_edit"
+ bl_options = {"DEFAULT_CLOSED"}
+
+ def draw(self, context):
+ layout = self.layout
+
+ if pt_buf.sws == "on":
+ layout.active = False
+ layout.label("Pen Tool Active")
+ 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",
+ icon="LAYER_USED").help_ids = "mesh_pen_tool"
+
+
+# Operator
+class pen_tool_operator(Operator):
+ bl_idname = "pen_tool.operator"
+ bl_label = "Pen Tool"
+ bl_options = {"REGISTER", "UNDO", "INTERNAL"}
+
+ text_location = IntProperty(
+ name="",
+ default=0,
+ options={'HIDDEN'}
+ )
+
+ @classmethod
+ def poll(cls, context):
+ # do not run in object mode
+ return (context.active_object and context.active_object.type == 'MESH' and
+ context.mode == 'EDIT_MESH')
+
+ def execute(self, context):
+ edit_mode_out()
+ ob_act = context.active_object
+ bme = bmesh.new()
+ bme.from_mesh(ob_act.data)
+
+ mtrx = ob_act.matrix_world.inverted() # ob_act matrix world inverted
+
+ # add vertices
+ list_ = []
+ for i in pt_buf.list_m_loc_3d:
+ bme.verts.new(mtrx * i)
+ bme.verts.index_update()
+ bme.verts.ensure_lookup_table()
+ list_.append(bme.verts[-1])
+
+ # add edges
+ n = len(list_)
+ for j in range(n - 1):
+ bme.edges.new((list_[j], list_[(j + 1) % n]))
+ bme.edges.index_update()
+
+ bme.to_mesh(ob_act.data)
+ store_restore_view(context, False)
+ edit_mode_in()
+
+ pt_buf.list_m_loc_2d[:] = []
+ pt_buf.list_m_loc_3d[:] = []
+ pt_buf.depth_location = Vector((0.0, 0.0, 0.0))
+ pt_buf.store_view_matrix = Matrix()
+ pt_buf.view_location = (0.0, 0.0, 0.0)
+ pt_buf.ctrl = False
+
+ context.area.tag_redraw()
+ return {'FINISHED'}
+
+ def modal(self, context, event):
+ context.area.tag_redraw()
+
+ # allow moving in the 3D View
+ if event.type in {
+ 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE',
+ 'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4', 'NUMPAD_6',
+ 'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9', 'NUMPAD_5'}:
+ return {'PASS_THROUGH'}
+
+ if event.type in {'LEFT_ALT', 'RIGHT_ALT'}:
+ if event.value == 'PRESS':
+ pt_buf.alt = True
+ if event.value == 'RELEASE':
+ pt_buf.alt = False
+ return {'RUNNING_MODAL'}
+
+ elif event.type in {'LEFT_CTRL', 'RIGHT_CTRL'}:
+ if event.value == 'PRESS':
+ pt_buf.ctrl = not pt_buf.ctrl
+ return {'RUNNING_MODAL'}
+
+ elif event.type in {'LEFT_SHIFT', 'RIGHT_SHIFT'}:
+ if event.value == 'PRESS':
+ pt_buf.shift = True
+ if event.value == 'RELEASE':
+ pt_buf.shift = False
+ return {'RUNNING_MODAL'}
+
+ elif event.type == 'MOUSEMOVE':
+ if pt_buf.list_m_loc_2d != []:
+ pt_buf_list_m_loc_3d_last_2d = location_3d_to_region_2d(
+ context.region,
+ context.space_data.region_3d,
+ pt_buf.list_m_loc_3d[-1]
+ )
+ if pt_buf.alt is True:
+ pt_buf.x = pt_buf_list_m_loc_3d_last_2d[0]
+ pt_buf.y = event.mouse_region_y
+ elif pt_buf.shift is True:
+ pt_buf.x = event.mouse_region_x
+ pt_buf.y = pt_buf_list_m_loc_3d_last_2d[1]
+ else:
+ pt_buf.x = event.mouse_region_x
+ pt_buf.y = event.mouse_region_y
+ else:
+ pt_buf.x = event.mouse_region_x
+ pt_buf.y = event.mouse_region_y
+
+ elif event.type == 'LEFTMOUSE':
+ if event.value == 'PRESS':
+ mouse_loc_2d = Vector((pt_buf.x, pt_buf.y))
+ pt_buf.list_m_loc_2d.append(mouse_loc_2d)
+
+ mouse_loc_3d = region_2d_to_location_3d(
+ context.region, context.space_data.region_3d,
+ mouse_loc_2d, pt_buf.depth_location
+ )
+ pt_buf.list_m_loc_3d.append(mouse_loc_3d)
+
+ pt_buf.depth_location = pt_buf.list_m_loc_3d[-1] # <-- depth location
+ # run Extrude at cursor
+ if pt_buf.ctrl:
+ try:
+ bpy.ops.mesh.dupli_extrude_cursor('INVOKE_DEFAULT', rotate_source=False)
+ except:
+ pass
+ elif event.value == 'RELEASE':
+ pass
+ elif event.type == 'RIGHTMOUSE':
+ context.space_data.draw_handler_remove(self._handle_px, 'WINDOW')
+ self.execute(context)
+ pt_buf.sws = 'off'
+ return {'FINISHED'}
+ elif event.type == 'ESC':
+ context.space_data.draw_handler_remove(self._handle_px, 'WINDOW')
+ store_restore_view(context, False)
+ pt_buf.list_m_loc_2d[:] = []
+ pt_buf.list_m_loc_3d[:] = []
+ pt_buf.depth_location = Vector((0.0, 0.0, 0.0))
+ pt_buf.sws = 'off'
+ pt_buf.store_view_matrix = Matrix()
+ pt_buf.view_location = (0.0, 0.0, 0.0)
+ pt_buf.ctrl = False
+ return {'CANCELLED'}
+
+ # Return has to be modal or the tool can crash
+ # It's better to define PASS_THROUGH as the exception and not the default
+ return {'RUNNING_MODAL'}
+
+ def invoke(self, context, event):
+ bme = bmesh.from_edit_mesh(context.active_object.data)
+ list_f = [f for f in bme.faces if f.select]
+
+ if len(list_f) != 0:
+ f = list_f[0]
+ pt_buf.depth_location = f.calc_center_median()
+ align_view_to_face_(context, bme, f)
+
+ if context.area.type == 'VIEW_3D':
+ # pre-compute the text location (thanks to the Carver add-on)
+ self.text_location = 100
+ overlap = context.user_preferences.system.use_region_overlap
+ for region in context.area.regions:
+ if region.type == "WINDOW":
+ self.text_location = region.width - 100
+ if overlap:
+ for region in context.area.regions:
+ if region.type == 'TOOL_PROPS':
+ self.text_location = self.text_location - region.width
+
+ if pt_buf.sws == 'on':
+ return {'RUNNING_MODAL'}
+ elif pt_buf.sws != 'on':
+ context.window_manager.modal_handler_add(self)
+ self._handle_px = context.space_data.draw_handler_add(
+ draw_callback_px,
+ (self, context),
+ 'WINDOW', 'POST_PIXEL'
+ )
+ pt_buf.sws = 'on'
+ return {'RUNNING_MODAL'}
+ else:
+ self.report({'WARNING'}, "Pen Tool: View3D not found, operation cancelled")
+ return {'CANCELLED'}
+
+
+class_list = [pen_tool_panel,
+ pen_tool_operator,
+ pen_tool_properties
+ ]
+
+
+def register():
+ for c in class_list:
+ bpy.utils.register_class(c)
+
+ bpy.types.Scene.pen_tool_props = PointerProperty(type=pen_tool_properties)
+
+ wm = bpy.context.window_manager
+ km = wm.keyconfigs.addon.keymaps.new(name='3D View', space_type='VIEW_3D')
+
+ # Note: left click + D key is reserved for Grease Pencil draw
+ kmi = km.keymap_items.new("pen_tool.operator", 'D', 'PRESS', ctrl=True)
+
+
+def unregister():
+ for c in class_list:
+ bpy.utils.unregister_class(c)
+
+ del bpy.types.Scene.pen_tool_props
+
+ wm = bpy.context.window_manager
+ km = wm.keyconfigs.addon.keymaps['3D View']
+ for kmi in km.keymap_items:
+ if kmi.idname == 'pen_tool.operator':
+ km.keymap_items.remove(kmi)
+ break
+
+
+if __name__ == "__main__":
+ register()