Use URL icon, add Tip: prefix, increase lower margin
[blender-addons-contrib.git] / mesh_edgetools.py
index 8223d9c8b0400b0f1fe09248563984ce3cbf4a23..e3bc3a3b6280d60ea02a03e4fdd8c515a5b72638 100644 (file)
 #       functionality, though it will sadly be a little clumsier to use due
 #       to Blender's selection limitations.
 #
-# Tasks:
-#   - Figure out how to do a GUI for "Shaft", especially for controlling radius.
+# Notes:
+#   - Buggy parts have been hidden behind bpy.app.debug.  Run Blender in debug
+#       to expose those.  Example: Shaft with more than two edges selected.
+#   - Some functions have started to crash, despite working correctly before.
+#       What could be causing that?  Blender bug?  Or coding bug?
 #
 # Paul "BrikBot" Marshall
 # Created: January 28, 2012
-# Last Modified: May 11, 2012
+# Last Modified: October 6, 2012
 # Homepage (blog): http://post.darkarsenic.com/
 #                       //blog.darkarsenic.com/
 #
-# Coded in IDLE, tested in Blender 2.63.
+# Coded in IDLE, tested in Blender 2.6.
 # Search for "@todo" to quickly find sections that need work.
 #
 # Remeber -
 # ^^ Maybe. . . . :P
 
 bl_info = {
-    'name': "EdgeTools",
-    'author': "Paul Marshall",
-    'version': (0, 8),
-    'blender': (2, 6, 3),
-    '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/Scripts/Modeling/EdgeTools",
-    'tracker_url': "https://blenderpython.svn.sourceforge.net/svnroot/blenderpython/scripts_library/scripts/addons_extern/mesh_edgetools.py",
-    'category': 'Mesh'}
+    "name": "EdgeTools",
+    "author": "Paul Marshall",
+    "version": (0, 8),
+    "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/"
+        "Scripts/Modeling/EdgeTools",
+    "tracker_url": "",
+    "category": "Mesh"}
+
 
 import bpy, bmesh, mathutils
-from math import pi, radians, sin, sqrt, tan
+from math import acos, pi, radians, sqrt, tan
 from mathutils import Matrix, Vector
 from mathutils.geometry import (distance_point_to_plane,
                                 interpolate_bezier,
@@ -91,6 +96,8 @@ from bpy.props import (BoolProperty,
                        FloatProperty,
                        EnumProperty)
 
+integrated = False
+
 # Quick an dirty method for getting the sign of a number:
 def sign(number):
     return (number > 0) - (number < 0)
@@ -100,7 +107,7 @@ def sign(number):
 #
 # Checks to see if two lines are parallel
 def is_parallel(v1, v2, v3, v4):
-    result = intersect_line_line(v1, v2, v3, v4) 
+    result = intersect_line_line(v1, v2, v3, v4)
     return result == None
 
 
@@ -136,6 +143,19 @@ def is_same_co(v1, v2):
     return True
 
 
+# is_face_planar
+#
+# Tests a face to see if it is planar.
+def is_face_planar(face, error = 0.0005):
+    for v in face.verts:
+        d = distance_point_to_plane(v.co, face.verts[0].co, face.normal)
+        if bpy.app.debug:
+            print("Distance: " + str(d))
+        if d < -error or d > error:
+            return False
+    return True
+
+
 # other_joined_edges
 #
 # Starts with an edge.  Then scans for linked, selected edges and builds a
@@ -145,10 +165,14 @@ def order_joined_edges(edge, edges = [], direction = 1):
         edges.append(edge)
         edges[0] = edge
 
-##    if bpy.app.debug:
-##        print(edge, end = ", ")
-##        print(edges, end = ", ")
-##        print(direction, end = "; ")
+    if bpy.app.debug:
+        print(edge, end = ", ")
+        print(edges, end = ", ")
+        print(direction, end = "; ")
+
+    # Robustness check: direction cannot be zero
+    if direction == 0:
+        direction = 1
 
     newList = []
     for e in edge.verts[0].link_edges:
@@ -163,7 +187,7 @@ def order_joined_edges(edge, edges = [], direction = 1):
                 newList.extend(order_joined_edges(e, edges, direction - 1))
 
     # This will only matter at the first level:
-    direction = direction 1
+    direction = direction * -1
 
     for e in edge.verts[1].link_edges:
         if e.select and edges.count(e) == 0:
@@ -176,9 +200,9 @@ def order_joined_edges(edge, edges = [], direction = 1):
                 newList.extend(edges)
                 newList.extend(order_joined_edges(e, edges, direction))
 
-##    if bpy.app.debug:
-##        print(newList, end = ", ")
-##        print(direction)
+    if bpy.app.debug:
+        print(newList, end = ", ")
+        print(direction)
 
     return newList
 
@@ -266,7 +290,7 @@ def interpolate_line_line(p1_co, p1_dir, p2_co, p2_dir, segments, tension = 1,
 # A quad may not be planar.  Therefore the treated definition of the surface is
 # that the surface is composed of all lines bridging two other lines defined by
 # the given four points.  The lines do not "cross".
-# 
+#
 # The two lines in 3-space can defined as:
 #   ┌  ┐         ┌   ┐     ┌   ┐  ┌  ┐         ┌   ┐     ┌   ┐
 #   │x1│         │a11│     │b11│  │x2│         │a21│     │b21│
@@ -303,12 +327,33 @@ def interpolate_line_line(p1_co, p1_dir, p2_co, p2_dir, segments, tension = 1,
 # http://www.mediafire.com/file/0egbr5ahg14talm/intersect_line_surface2.nb for
 # Mathematica computation).
 #
+# Additionally, the resulting series of equations may result in a div by zero
+# exception if the line in question if parallel to one of the axis or if the
+# quad is planar and parallel to either the XY, XZ, or YZ planes.  However, the
+# system is still solvable but must be dealt with a little differently to avaid
+# these special cases.  Because the resulting equations are a little different,
+# we have to code them differently.  Hence the special cases.
+#
 # Tri math and theory:
 # A triangle must be planar (three points define a plane).  Therefore we just
 # have to make sure that the line intersects inside the triangle.
-def intersect_line_face(edge, face):
-    # If we are dealing with a quad:
-    if len(face.verts) == 4:
+#
+# If the point is within the triangle, then the angle between the lines that
+# connect the point to the each individual point of the triangle will be
+# equal to 2 * PI.  Otherwise, if the point is outside the triangle, then the
+# sum of the angles will be less.
+#
+# @todo
+#   - Figure out how to deal with n-gons.  How the heck is a face with 8 verts
+#       definied mathematically?  How do I then find the intersection point of
+#       a line with said vert?  How do I know if that point is "inside" all the
+#       verts?  I have no clue, and haven't been able to find anything on it so
+#       far.  Maybe if someone (actually reads this and) who knows could note?
+def intersect_line_face(edge, face, is_infinite = False, error = 0.000002):
+    int_co = None
+
+    # If we are dealing with a non-planar quad:
+    if len(face.verts) == 4 and not is_face_planar(face):
         edgeA = face.edges[0]
         edgeB = None
         flipB = False
@@ -338,7 +383,6 @@ def intersect_line_face(edge, face):
         else:
             a21, a22, a23 = edgeB.verts[0].co[0], edgeB.verts[0].co[1], edgeB.verts[0].co[2]
             b21, b22, b23 = edgeB.verts[1].co[0], edgeB.verts[1].co[1], edgeB.verts[1].co[2]
-
         a31, a32, a33 = edge.verts[0].co[0], edge.verts[0].co[1], edge.verts[0].co[2]
         b31, b32, b33 = edge.verts[1].co[0], edge.verts[1].co[1], edge.verts[1].co[2]
 
@@ -405,7 +449,6 @@ def intersect_line_face(edge, face):
         m58 = a32 * b21 * b33
         m59 = a11 * b22 * b33
         m60 = a31 * b22 * b33
-
         m61 = a33 * b12 * b21
         m62 = a32 * b13 * b21
         m63 = a33 * b11 * b22
@@ -418,7 +461,6 @@ def intersect_line_face(edge, face):
         m70 = b11 * b23 * b32
         m71 = b12 * b21 * b33
         m72 = b11 * b22 * b33
-
         n01 = m01 - m02 - m03 + m04 + m05 - m06
         n02 = -m07 + m08 + m09 - m10 - m11 + m12 + m13 - m14 - m15 + m16 + m17 - m18 - m25 + m27 + m29 - m31 + m39 - m41 - m43 + m45 - m53 + m55 + m57 - m59
         n03 = -m19 + m20 + m33 - m34 - m47 + m48
@@ -430,25 +472,87 @@ def intersect_line_face(edge, face):
 
         # Calculate t, t12, and t3:
         t = (n07 - sqrt(pow(-n07, 2) - 4 * (n01 + n03 + n04) * n08)) / (2 * n08)
+
         # t12 can be greatly simplified by defining it with t in it:
-        t12 = -(-(a32 - b32) * (-a31 + a11 * (1 - t) + b11 * t) + (a31 - b31) * (-a32 + a12 * (1 - t) + b12 * t)) / (-(a32 - b32) * (-a11 * (1 - t) + a21 * (1 - t) - b11 * t + b21 * t) + (a31 - b31) * (-a12 * (1 - t) + a22 * (1 - t) - b12 * t + b22 * t))
+        # If block used to help prevent any div by zero error.
+        t12 = 0
+
+        if a31 == b31:
+            # The line is parallel to the z-axis:
+            if a32 == b32:
+                t12 = ((a11 - a31) + (b11 - a11) * t) / ((a21 - a11) + (a11 - a21 - b11 + b21) * t)
+            # The line is parallel to the y-axis:
+            elif a33 == b33:
+                t12 = ((a11 - a31) + (b11 - a11) * t) / ((a21 - a11) + (a11 - a21 - b11 + b21) * t)
+            # The line is along the y/z-axis but is not parallel to either:
+            else:
+                t12 = -(-(a33 - b33) * (-a32 + a12 * (1 - t) + b12 * t) + (a32 - b32) * (-a33 + a13 * (1 - t) + b13 * t)) / (-(a33 - b33) * ((a22 - a12) * (1 - t) + (b22 - b12) * t) + (a32 - b32) * ((a23 - a13) * (1 - t) + (b23 - b13) * t))
+        elif a32 == b32:
+            # The line is parallel to the x-axis:
+            if a33 == b33:
+                t12 = ((a12 - a32) + (b12 - a12) * t) / ((a22 - a12) + (a12 - a22 - b12 + b22) * t)
+            # The line is along the x/z-axis but is not parallel to either:
+            else:
+                t12 = -(-(a33 - b33) * (-a31 + a11 * (1 - t) + b11 * t) + (a31 - b31) * (-a33 + a13 * (1 - t) + b13 * t)) / (-(a33 - b33) * ((a21 - a11) * (1 - t) + (b21 - b11) * t) + (a31 - b31) * ((a23 - a13) * (1 - t) + (b23 - b13) * t))
+        # The line is along the x/y-axis but is not parallel to either:
+        else:
+            t12 = -(-(a32 - b32) * (-a31 + a11 * (1 - t) + b11 * t) + (a31 - b31) * (-a32 + a12 * (1 - t) + b12 * t)) / (-(a32 - b32) * ((a21 - a11) * (1 - t) + (b21 - b11) * t) + (a31 - b31) * ((a22 - a21) * (1 - t) + (b22 - b12) * t))
+
         # Likewise, t3 is greatly simplified by defining it in terms of t and t12:
-        t3 = (-a11 + a31 + (a11 * t) - (b11 * t) + (a11 * t12) - (a21 * t12) - (a11 * t * t12) + (a21 * t * t12) + (b11 * t * t12) - (b21 * t * t12)) / (a31 - b31)
+        # If block used to prevent a div by zero error.
+        t3 = 0
+        if a31 != b31:
+            t3 = (-a11 + a31 + (a11 - b11) * t + (a11 - a21) * t12 + (a21 - a11 + b11 - b21) * t * t12) / (a31 - b31)
+        elif a32 != b32:
+            t3 = (-a12 + a32 + (a12 - b12) * t + (a12 - a22) * t12 + (a22 - a12 + b12 - b22) * t * t12) / (a32 - b32)
+        elif a33 != b33:
+            t3 = (-a13 + a33 + (a13 - b13) * t + (a13 - a23) * t12 + (a23 - a13 + b13 - b23) * t * t12) / (a33 - b33)
+        else:
+            print("The second edge is a zero-length edge")
+            return None
 
         # Calculate the point of intersection:
         x = (1 - t3) * a31 + t3 * b31
         y = (1 - t3) * a32 + t3 * b32
         z = (1 - t3) * a33 + t3 * b33
+        int_co = Vector((x, y, z))
 
-        int_co = [True, Vector((x, y, z))]
+        if bpy.app.debug:
+            print(int_co)
 
         # If the line does not intersect the quad, we return "None":
-        if t < 0 or t > 1 or t12 < 0 or t12 > 1:
-            int_co[0] = False
+        if (t < -1 or t > 1 or t12 < -1 or t12 > 1) and not is_infinite:
+            int_co = None
 
-        return int_co
     elif len(face.verts) == 3:
-        return int_co
+        p1, p2, p3 = face.verts[0].co, face.verts[1].co, face.verts[2].co
+        int_co = intersect_line_plane(edge.verts[0].co, edge.verts[1].co, p1, face.normal)
+
+        # Only check if the triangle is not being treated as an infinite plane:
+        # Math based from http://paulbourke.net/geometry/linefacet/
+        if int_co != None and not is_infinite:
+            pA = p1 - int_co
+            pB = p2 - int_co
+            pC = p3 - int_co
+            # These must be unit vectors, else we risk a domain error:
+            pA.length = 1
+            pB.length = 1
+            pC.length = 1
+            aAB = acos(pA.dot(pB))
+            aBC = acos(pB.dot(pC))
+            aCA = acos(pC.dot(pA))
+            sumA = aAB + aBC + aCA
+
+            # If the point is outside the triangle:
+            if (sumA > (pi + error) and sumA < (pi - error)):
+                int_co = None
+
+    # This is the default case where we either have a planar quad or an n-gon.
+    else:
+        int_co = intersect_line_plane(edge.verts[0].co, edge.verts[1].co,
+                                      face.verts[0].co, face.normal)
+
+    return int_co
 
 
 # project_point_plane
@@ -459,7 +563,7 @@ def project_point_plane(pt, plane_co, plane_no):
     proj_co = intersect_line_plane(pt, pt + plane_no, plane_co, plane_no)
     proj_ve = proj_co - pt
     return (proj_ve, proj_co)
-    
+
 
 # ------------ FILLET/CHAMPHER HELPER METHODS -------------
 
@@ -490,13 +594,19 @@ def is_planar_edge(edge, error = 0.000002):
     return (angle < error and angle > -error) or (angle < (180 + error) and angle > (180 - error))
 
 
-# fillet_geom_data
+# fillet_axis
+#
+# Calculates the base geometry data for the fillet. This assumes that the faces
+# are planar:
+#
+# @todo
+#   - Redesign so that the faces do not have to be planar
 #
-# Calculates the base geometry data for the fillet.  The seems to be issues
-# some of the vector math right now.  Will need to be debuged.
+# There seems to be issues some of the vector math right now.  Will need to be
+# debuged.
 def fillet_axis(edge, radius):
     vectors = [None, None, None, None]
-    
+
     origin = Vector((0, 0, 0))
     axis = edge.verts[1].co - edge.verts[0].co
 
@@ -529,10 +639,10 @@ def fillet_axis(edge, radius):
     # Get the normal for face 0 and face 1:
     norm1 = edge.link_faces[0].normal
     norm2 = edge.link_faces[1].normal
-    
+
     # We need to find the angle between the two faces, then bisect it:
     theda = (pi - edge.calc_face_angle()) / 2
-    
+
     # We are dealing with a triangle here, and we will need the length
     # of its adjacent side.  The opposite is the radius:
     adj_len = radius / tan(theda)
@@ -545,13 +655,17 @@ def fillet_axis(edge, radius):
         vectors[i] = project_point_plane(vectors[i], origin, axis)[1]
         vectors[i].length = adj_len
         vectors[i] = vectors[i] + edge.verts[i % 2].co
-    
+
     # Compute fillet axis end points:
     v1 = intersect_line_line(vectors[0], vectors[0] + norm1, vectors[2], vectors[2] + norm2)[0]
     v2 = intersect_line_line(vectors[1], vectors[1] + norm1, vectors[3], vectors[3] + norm2)[0]
     return [v1, v2]
 
 
+def fillet_point(t, face1, face2):
+    return
+
+
 # ------------------- EDGE TOOL METHODS -------------------
 
 # Extends an "edge" in two directions:
@@ -579,7 +693,7 @@ class Extend(bpy.types.Operator):
         layout.prop(self, "di1")
         layout.prop(self, "di2")
         layout.prop(self, "length")
-    
+
 
     @classmethod
     def poll(cls, context):
@@ -590,7 +704,7 @@ class Extend(bpy.types.Operator):
     def invoke(self, context, event):
         return self.execute(context)
 
-    
+
     def execute(self, context):
         bpy.ops.object.editmode_toggle()
         bm = bmesh.new()
@@ -607,7 +721,7 @@ class Extend(bpy.types.Operator):
             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:
@@ -662,7 +776,7 @@ class Spline(bpy.types.Operator):
     bl_label = "Spline"
     bl_description = "Create a spline interplopation between two edges"
     bl_options = {'REGISTER', 'UNDO'}
-    
+
     alg = EnumProperty(name = "Spline Algorithm",
                        items = [('Blender', 'Blender', 'Interpolation provided through \"mathutils.geometry\"'),
                                 ('Hermite', 'C-Spline', 'C-spline interpolation'),
@@ -713,7 +827,7 @@ class Spline(bpy.types.Operator):
     def invoke(self, context, event):
         return self.execute(context)
 
-    
+
     def execute(self, context):
         bpy.ops.object.editmode_toggle()
         bm = bmesh.new()
@@ -722,7 +836,7 @@ class Spline(bpy.types.Operator):
 
         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)]
@@ -748,7 +862,7 @@ class Spline(bpy.types.Operator):
         else:
             v2 = verts[2]
             p2_co = verts[2].co
-            p2_dir = verts[3].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
@@ -791,13 +905,13 @@ class Spline(bpy.types.Operator):
 #
 # @todo Change method from a cross product to a rotation matrix to make the
 #   angle part work.
-#   --- todo completed Feb 4th, but still needs work ---
+#   --- todo completed 2/4/2012, but still needs work ---
 # @todo Figure out a way to make +/- predictable
 #   - Maybe use angel between edges and vector direction definition?
 #   --- TODO COMPLETED ON 2/9/2012 ---
 class Ortho(bpy.types.Operator):
     bl_idname = "mesh.edgetools_ortho"
-    bl_label = "Angle off Edge"
+    bl_label = "Angle Off Edge"
     bl_description = ""
     bl_options = {'REGISTER', 'UNDO'}
 
@@ -848,7 +962,7 @@ class Ortho(bpy.types.Operator):
         row.prop(self, "neg")
         layout.prop(self, "angle")
         layout.prop(self, "length")
-    
+
     @classmethod
     def poll(cls, context):
         ob = context.active_object
@@ -858,7 +972,7 @@ class Ortho(bpy.types.Operator):
     def invoke(self, context, event):
         return self.execute(context)
 
-    
+
     def execute(self, context):
         bpy.ops.object.editmode_toggle()
         bm = bmesh.new()
@@ -873,7 +987,8 @@ class Ortho(bpy.types.Operator):
         # Until I can figure out a better way of handeling it:
         if len(edges) < 2:
             bpy.ops.object.editmode_toggle()
-            self.report({'ERROR_INVALID_INPUT'}, "You must select two edges.")
+            self.report({'ERROR_INVALID_INPUT'},
+                        "You must select two edges.")
             return {'CANCELLED'}
 
         verts = [edges[0].verts[0],
@@ -885,7 +1000,8 @@ class Ortho(bpy.types.Operator):
 
         # If the two edges are parallel:
         if cos == None:
-            self.report({'WARNING'}, "Selected lines are parallel: results may be unpredictable.")
+            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]))
@@ -894,7 +1010,8 @@ class Ortho(bpy.types.Operator):
         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.")
+                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:
@@ -904,7 +1021,7 @@ class Ortho(bpy.types.Operator):
 
             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]))
 
@@ -930,7 +1047,7 @@ class Ortho(bpy.types.Operator):
         # 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 with in "bpy.data" with the ".add"
+        # It looks like I might be able to do this within "bpy.data" with the ".add"
         # function.
         # ------- BMESH UPDATE -------
         # BMesh uses ".new()"
@@ -962,7 +1079,16 @@ class Shaft(bpy.types.Operator):
     bl_description = "Create a shaft mesh around an axis"
     bl_options = {'REGISTER', 'UNDO'}
 
+    # Selection defaults:
     shaftType = 0
+
+    # For tracking if the user has changed selection:
+    last_edge = IntProperty(name = "Last Edge",
+                            description = "Tracks if user has changed selected edge",
+                            min = 0, max = 1,
+                            default = 0)
+    last_flip = False
+
     edge = IntProperty(name = "Edge",
                        description = "Edge to shaft around.",
                        min = 0, max = 1,
@@ -1009,9 +1135,13 @@ class Shaft(bpy.types.Operator):
 
 
     def invoke(self, context, event):
+        # Make sure these get reset each time we run:
+        self.last_edge = 0
+        self.edge = 0
+
         return self.execute(context)
 
-    
+
     def execute(self, context):
         bpy.ops.object.editmode_toggle()
         bm = bmesh.new()
@@ -1027,12 +1157,7 @@ class Shaft(bpy.types.Operator):
         verts = []
 
         # Pre-caclulated values:
-        
-        # Selects which edge to use
-        if self.edge == 0:
-            edge = [0, 1]
-        else:
-            edge = [1, 0]
+
         rotRange = [radians(self.start), radians(self.finish)]
         rads = radians((self.finish - self.start) / self.segments)
 
@@ -1041,23 +1166,64 @@ class Shaft(bpy.types.Operator):
 
         edges = [e for e in bEdges if e.select]
 
-        verts.append(edges[edge[0]].verts[0])
-        verts.append(edges[edge[0]].verts[1])
+        # 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'}
 
+        # If two edges are selected:
         if len(edges) == 2:
+            # default:
+            edge = [0, 1]
+            vert = [0, 1]
+
+            # Edge selection:
+            #
+            # 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:
+                    bpy.ops.object.editmode_toggle()
+                    self.report({'ERROR_INVALID_INPUT'},
+                                "Active geometry is not an edge.")
+                    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.append(edges[edge[1]].verts[1])
-                verts.append(edges[edge[1]].verts[0])
-            else:
-                verts.append(edges[edge[1]].verts[0])
-                verts.append(edges[edge[1]].verts[1])
+                verts = [1, 0]
+
+            verts.append(edges[edge[1]].verts[vert[0]])
+            verts.append(edges[edge[1]].verts[vert[1]])
+
             self.shaftType = 0
-        elif len(edges) > 2:
+        # 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 len(edges) > 2 and bpy.app.debug:
             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])
+                edges = order_joined_edges(edges[0])
                 verts = []
                 for e in edges:
                     if verts.count(e.verts[0]) == 0:
@@ -1065,10 +1231,15 @@ class Shaft(bpy.types.Operator):
                     if verts.count(e.verts[1]) == 0:
                         verts.append(e.verts[1])
             else:
-                self.report({'ERROR_INVALID_INPUT'}, "Active geometry is not an edge.")
+                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)
@@ -1084,11 +1255,12 @@ class Shaft(bpy.types.Operator):
         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))
+        # 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 = []
@@ -1099,17 +1271,17 @@ class Shaft(bpy.types.Operator):
             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):
-                    # These will be rotated about the orgin so will need to be shifted:
                     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):
-                    # These will be rotated about the orgin so will need to be shifted:
                     verts_out.append(co - (matrices[j] * init_vec))
-        # Else if a line and a point was selected:    
+        # 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:
@@ -1156,8 +1328,9 @@ class Shaft(bpy.types.Operator):
                     e.select = True
 
             # Faces:
-##            for i in range(numE):
-##                for j in range(len(verts)):
+            # 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()
@@ -1191,12 +1364,20 @@ class Shaft(bpy.types.Operator):
 
 
 # "Slices" edges crossing a plane defined by a face.
+# @todo Selecting a face as the cutting plane will cause Blender to crash when
+#   using "Rip".
 class Slice(bpy.types.Operator):
     bl_idname = "mesh.edgetools_slice"
     bl_label = "Slice"
     bl_description = "Cuts edges at the plane defined by a selected face."
     bl_options = {'REGISTER', 'UNDO'}
 
+    make_copy = BoolProperty(name = "Make Copy",
+                             description = "Make new vertices at intersection points instead of spliting the edge",
+                             default = False)
+    rip = BoolProperty(name = "Rip",
+                       description = "Split into two edges that DO NOT share an intersection vertice.",
+                       default = False)
     pos = BoolProperty(name = "Positive",
                        description = "Remove the portion on the side of the face normal",
                        default = False)
@@ -1207,9 +1388,12 @@ class Slice(bpy.types.Operator):
     def draw(self, context):
         layout = self.layout
 
-        layout.label("Remove Side:")
-        layout.prop(self, "pos")
-        layout.prop(self, "neg")
+        layout.prop(self, "make_copy")
+        if not self.make_copy:
+            layout.prop(self, "rip")
+            layout.label("Remove Side:")
+            layout.prop(self, "pos")
+            layout.prop(self, "neg")
 
 
     @classmethod
@@ -1221,7 +1405,7 @@ class Slice(bpy.types.Operator):
     def invoke(self, context, event):
         return self.execute(context)
 
-    
+
     def execute(self, context):
         bpy.ops.object.editmode_toggle()
         bm = bmesh.new()
@@ -1233,48 +1417,126 @@ class Slice(bpy.types.Operator):
         bEdges = bm.edges
         bFaces = bm.faces
 
-        fVerts = []
+        face = None
         normal = None
 
         # 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
+        #   - First check to use the active face.  This allows users to just
+        #       select a bunch of faces with the last being the cutting plane.
+        #       This is try and make the tool act more like a built-in Blender
+        #       function.
+        #   - 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, we have problem.  Exit:
+        if face == 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.  We can work with it, but it
+        # 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.")
+
+        # @todo DEBUG TRACKER - DELETE WHEN FINISHED:
+        dbg = 0
+        if bpy.app.debug:
+            print(len(bEdges))
 
+        # Iterate over the edges:
         for e in bEdges:
-            if e.select:
-                v1 = e.verts[0]
-                v2 = e.verts[1]
-                if v1 in fVerts and v2 in fVerts:
-                    e.select = False
-                    continue
-                intersection = intersect_line_plane(v1.co, v2.co, fVerts[0].co, normal)
+            # @todo DEBUG TRACKER - DELETE WHEN FINISHED:
+            if bpy.app.debug:
+                print(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)
+
+                # More debug info - I think this can stay.
+                if bpy.app.debug:
+                    print("Intersection", end = ': ')
+                    print(intersection)
+
+                # If an intersection exists find the distance of each of the end
+                # points from the plane, with "positive" being in the direction
+                # of the cutting plane's normal.  If the points are on opposite
+                # side of the plane, then it intersects and we need to cut it.
                 if intersection != None:
-                    d1 = distance_point_to_plane(v1.co, fVerts[0].co, normal)
-                    d2 = distance_point_to_plane(v2.co, fVerts[0].co, normal)
-                    # If they have different signs, then the edge crosses the plane:
+                    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 vertice the positive vertice:
                         if d1 < d2:
                             v2, v1 = v1, v2
-                        new = list(bmesh.utils.edge_split(e, v1, 0.5))
-                        new[1].co = intersection
-                        e.select = False
-                        new[0].select = False
-                        if self.pos:
-                            bEdges.remove(new[0])
-                        if self.neg:
+                        if self.make_copy:
+                            new = bVerts.new()
+                            new.co = intersection
+                            new.select = True
+                        elif self.rip:
+                            newV1 = bVerts.new()
+                            newV1.co = intersection
+
+                            if bpy.app.debug:
+                                print("newV1 created", end = '; ')
+
+                            newV2 = bVerts.new()
+                            newV2.co = intersection
+
+                            if bpy.app.debug:
+                                print("newV2 created", end = '; ')
+
+                            newE1 = bEdges.new((v1, newV1))
+                            newE2 = bEdges.new((v2, newV2))
+
+                            if bpy.app.debug:
+                                print("new edges created", end = '; ')
+
                             bEdges.remove(e)
 
+                            if bpy.app.debug:
+                                print("old edge removed.")
+                                print("We're done with this edge.")
+                        else:
+                            new = list(bmesh.utils.edge_split(e, v1, 0.5))
+                            new[1].co = intersection
+                            e.select = False
+                            new[0].select = False
+                            if self.pos:
+                                bEdges.remove(new[0])
+                            if self.neg:
+                                bEdges.remove(e)
+
         bm.to_mesh(context.active_object.data)
         bpy.ops.object.editmode_toggle()
         return {'FINISHED'}
 
 
+# This projects the selected edges onto the selected plane.  This projects both
+# points on the selected edge.
 class Project(bpy.types.Operator):
     bl_idname = "mesh.edgetools_project"
     bl_label = "Project"
@@ -1312,6 +1574,7 @@ class Project(bpy.types.Operator):
         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:
@@ -1494,24 +1757,35 @@ class Fillet(bpy.types.Operator):
 
     radius = FloatProperty(name = "Radius",
                            description = "Radius of the edge fillet",
-                           min = 0.00001, max = 1024.0, softmax = 2.0,
+                           min = 0.00001, max = 1024.0,
                            default = 0.5)
     prop = EnumProperty(name = "Propagation",
                         items = [("m", "Minimal", "Minimal edge propagation"),
                                  ("t", "Tangential", "Tangential edge propagation")],
                         default = "m")
+    prop_fac = FloatProperty(name = "Propagation Factor",
+                             description = "Corner detection sensitivity factor for tangential propagation",
+                             min = 0.0, max = 100.0,
+                             default = 25.0)
+    deg_seg = FloatProperty(name = "Degrees/Section",
+                            description = "Approximate degrees per section",
+                            min = 0.00001, max = 180.0,
+                            default = 10.0)
     res = IntProperty(name = "Resolution",
                       description = "Resolution of the fillet",
-                      min = 1, max = 1024, softmax = 128,
+                      min = 1, max = 1024,
                       default = 8)
 
     def draw(self, context):
         layout = self.layout
         layout.prop(self, "radius")
         layout.prop(self, "prop")
+        if self.prop == "t":
+            layout.prop(self, "prop_fac")
+        layout.prop(self, "deg_seg")
         layout.prop(self, "res")
 
-    
+
     @classmethod
     def poll(cls, context):
         ob = context.active_object
@@ -1532,21 +1806,104 @@ class Fillet(bpy.types.Operator):
         bEdges = bm.edges
         bVerts = bm.verts
 
+        # Robustness check: this does not support n-gons (at least for now)
+        # because I have no idea how to handle them righ now.  If there is
+        # an n-gon in the mesh, warn the user that results may be nuts because
+        # of it.
+        #
+        # I'm not going to cause it to exit if there are n-gons, as they may
+        # not be encountered.
+        # @todo I would like this to be a confirmation dialoge of some sort
+        # @todo I would REALLY like this to just handle n-gons. . . .
+        for f in bFaces:
+            if len(face.verts) > 4:
+                self.report({'WARNING'},
+                            "Mesh contains n-gons which are not supported. Operation may fail.")
+                break
+
         # Get the selected edges:
+        # Robustness check: boundary and wire edges are not fillet-able.
         edges = [e for e in bEdges if e.select and not e.is_boundary and not e.is_wire]
 
         for e in edges:
-            points = fillet_axis(e, self.radius)
-            
+            axis_points = fillet_axis(e, self.radius)
+
+
+        bm.to_mesh(bpy.context.active_object.data)
+        bpy.ops.object.editmode_toggle()
+        return {'FINISHED'}
+
+
+# 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 running
+# Blender in debug mode.
+# So far no errors have been found. Thanks to anyone who tests and reports bugs!
+class Intersect_Line_Face(bpy.types.Operator):
+    bl_idname = "mesh.edgetools_ilf"
+    bl_label = "ILF TEST"
+    bl_description = "TEST ONLY: INTERSECT_LINE_FACE"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    @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):
+        # Make sure we really are in debug mode:
+        if not bpy.app.debug:
+            self.report({'ERROR_INVALID_INPUT'},
+                        "This is for debugging only: you should not be able to run this!")
+            return {'CANCELLED'}
+
+        bpy.ops.object.editmode_toggle()
+        bm = bmesh.new()
+        bm.from_mesh(bpy.context.active_object.data)
+        bm.normal_update()
+
+        bFaces = bm.faces
+        bEdges = bm.edges
+        bVerts = bm.verts
+
+        face = None
+        for f in bFaces:
+            if f.select:
+                face = f
+                break
+
+        edge = None
+        for e in bEdges:
+            if e.select and not e in face.edges:
+                edge = e
+                break
+
+        point = intersect_line_face(edge, face, True)
+
+        if point != None:
+            new = bVerts.new()
+            new.co = point
+        else:
+            bpy.ops.object.editmode_toggle()
+            self.report({'ERROR_INVALID_INPUT'}, "point was \"None\"")
+            return {'CANCELLED'}
+
+        bm.to_mesh(bpy.context.active_object.data)
+        bpy.ops.object.editmode_toggle()
         return {'FINISHED'}
 
 
 class VIEW3D_MT_edit_mesh_edgetools(bpy.types.Menu):
     bl_label = "EdgeTools"
-    
+
     def draw(self, context):
+        global integrated
         layout = self.layout
-        
+
         layout.operator("mesh.edgetools_extend")
         layout.operator("mesh.edgetools_spline")
         layout.operator("mesh.edgetools_ortho")
@@ -1554,6 +1911,17 @@ class VIEW3D_MT_edit_mesh_edgetools(bpy.types.Menu):
         layout.operator("mesh.edgetools_slice")
         layout.operator("mesh.edgetools_project")
         layout.operator("mesh.edgetools_project_end")
+        if bpy.app.debug:
+            ## Not ready for prime-time yet:
+            layout.operator("mesh.edgetools_fillet")
+            ## For internal testing ONLY:
+            layout.operator("mesh.edgetools_ilf")
+        # If TinyCAD VTX exists, add it to the menu.
+        # @todo This does not work.
+        if integrated and bpy.app.debug:
+            layout.operator(EdgeIntersections.bl_idname, text="Edges V Intersection").mode = -1
+            layout.operator(EdgeIntersections.bl_idname, text="Edges T Intersection").mode = 0
+            layout.operator(EdgeIntersections.bl_idname, text="Edges X Intersection").mode = 1
 
 
 def menu_func(self, context):
@@ -1569,18 +1937,29 @@ classes = [VIEW3D_MT_edit_mesh_edgetools,
     Shaft,
     Slice,
     Project,
-    Project_End]
+    Project_End,
+    Fillet,
+    Intersect_Line_Face]
 
 
 # registering and menu integration
 def register():
-    if int(bpy.app.build_revision[0:5]) < 44800:
-        print("Error in Edgetools:")
-        print("This version of Blender does not support the necessary BMesh API.")
-        print("Please download a newer build at http://www.graphicall.org")
-        return {'ERROR'}
+    global integrated
+
     for c in classes:
         bpy.utils.register_class(c)
+
+    # I would like this script to integrate the TinyCAD VTX menu options into
+    # the edge tools menu if it exists.  This should make the UI a little nicer
+    # for users.
+    # @todo Remove TinyCAD VTX menu entries and add them too EdgeTool's menu
+    import inspect, os.path
+
+    path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
+    if os.path.isfile(path + "\mesh_edge_intersection_tools.py"):
+        print("EdgeTools UI integration test - TinyCAD VTX Found")
+        integrated = True
+
     bpy.types.VIEW3D_MT_edit_mesh_specials.prepend(menu_func)
 
 
@@ -1588,9 +1967,10 @@ def register():
 def unregister():
     for c in classes:
         bpy.utils.unregister_class(c)
+
     bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func)
 
 
 if __name__ == "__main__":
     register()
-    
+