2
BEGIN GPL LICENSE BLOCK
4
This program is free software; you can redistribute it and/or
5
modify it under the terms of the GNU General Public License
6
as published by the Free Software Foundation; either version 2
7
of the License, or (at your option) any later version.
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
GNU General Public License for more details.
14
You should have received a copy of the GNU General Public License
15
along with this program; if not, write to the Free Software Foundation,
16
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
"name": "Edge tools : tinyCAD VTX",
27
"location": "View3D > EditMode > (w) Specials",
28
"warning": "Still under development",
29
"wiki_url": "http://wiki.blender.org/index.php/"\
30
"Extensions:2.5/Py/Scripts/Modeling/Edge_Slice",
31
"tracker_url": "http://projects.blender.org/tracker/"\
32
"?func=detail&aid=25227"
36
parts based on Keith (Wahooney) Boshoff, cursor to intersection script and
37
Paul Bourke's Shortest Line Between 2 lines, and thanks to PKHG from BA.org
38
for attempting to explain things to me that i'm not familiar with.
39
TODO: [ ] allow multi selection ( > 2 ) for Slice/Weld intersection mode
40
TODO: [ ] streamline this code !
42
1) Edge Extend To Edge ( T )
43
2) Edge Slice Intersecting ( X )
44
3) Edge Project Converging ( V )
50
from mathutils import Vector, geometry
51
from mathutils.geometry import intersect_line_line as LineIntersect
53
VTX_PRECISION = 1.0e-5 # or 1.0e-6 ..if you need
55
# returns distance between two given points
56
def mDist(A, B): return (A-B).length
59
# returns True / False if a point happens to lie on an edge
60
def isPointOnEdge(point, A, B):
61
eps = ((mDist(A, B) - mDist(point,B)) - mDist(A,point))
62
if abs(eps) < VTX_PRECISION: return True
64
print('distance is ' + str(eps))
68
# returns the number of edges that a point lies on.
69
def CountPointOnEdges(point, outer_points):
71
if(isPointOnEdge(point, outer_points[0][0], outer_points[0][1])): count+=1
72
if(isPointOnEdge(point, outer_points[1][0], outer_points[1][1])): count+=1
76
# takes Vector List and returns tuple of points in expected order.
77
def edges_to_points(edges):
78
(vp1, vp2) = (Vector((edges[0][0])), Vector((edges[0][1])))
79
(vp3, vp4) = (Vector((edges[1][0])), Vector((edges[1][1])))
80
return (vp1,vp2,vp3,vp4)
83
# takes a list of 4 vectors and returns True or False depending on checks
84
def checkIsMatrixCoplanar(verti):
85
(v1, v2, v3, v4) = edges_to_points(verti) #unpack
86
shortest_line = LineIntersect(v1, v2, v3, v4)
87
if mDist(shortest_line[1], shortest_line[0]) > VTX_PRECISION: return False
91
# point = the halfway mark on the shortlest line between two lines
92
def checkEdges(Edge, obj):
93
(p1, p2, p3, p4) = edges_to_points(Edge)
94
line = LineIntersect(p1, p2, p3, p4)
95
point = ((line[0] + line[1]) / 2) # or point = line[0]
98
# returns (object, number of verts, number of edges) && object mode == True
99
def GetActiveObject():
100
bpy.ops.object.mode_set(mode='EDIT')
101
bpy.ops.mesh.delete(type='EDGE') # removes edges + verts
102
(vert_count, edge_count) = getVertEdgeCount()
103
(vert_num, edge_num) = (len(vert_count),len(edge_count))
105
bpy.ops.object.mode_set(mode='OBJECT') # to be sure.
106
o = bpy.context.active_object
107
return (o, vert_num, edge_num)
110
def AddVertsToObject(vert_count, o, mvX, mvA, mvB, mvC, mvD):
111
o.data.vertices.add(5)
112
pointlist = [mvX, mvA, mvB, mvC, mvD]
113
for vpoint in range(len(pointlist)):
114
o.data.vertices[vert_count+vpoint].co = pointlist[vpoint]
117
# Used when the user chooses to slice/Weld, vX is intersection point
118
def makeGeometryWeld(vX,outer_points):
119
(o, vert_count, edge_count) = GetActiveObject()
120
(vA, vB, vC, vD) = edges_to_points(outer_points)
121
AddVertsToObject(vert_count, o, vA, vX, vB, vC, vD) # o is the object
125
oe[edge_count].vertices = [vert_count,vert_count+1]
126
oe[edge_count+1].vertices = [vert_count+2,vert_count+1]
127
oe[edge_count+2].vertices = [vert_count+3,vert_count+1]
128
oe[edge_count+3].vertices = [vert_count+4,vert_count+1]
131
# Used for extending an edge to a point on another edge.
132
def ExtendEdge(vX, outer_points, count):
133
(o, vert_count, edge_count) = GetActiveObject()
134
(vA, vB, vC, vD) = edges_to_points(outer_points)
135
AddVertsToObject(vert_count, o, vX, vA, vB, vC, vD)
139
# Candidate for serious optimization.
140
if isPointOnEdge(vX, vA, vB):
141
oe[edge_count].vertices = [vert_count, vert_count+1]
142
oe[edge_count+1].vertices = [vert_count, vert_count+2]
143
# find which of C and D is farthest away from X
144
if mDist(vD, vX) > mDist(vC, vX):
145
oe[edge_count+2].vertices = [vert_count, vert_count+3]
146
oe[edge_count+3].vertices = [vert_count+3, vert_count+4]
147
if mDist(vC, vX) > mDist(vD, vX):
148
oe[edge_count+2].vertices = [vert_count, vert_count+4]
149
oe[edge_count+3].vertices = [vert_count+3, vert_count+4]
151
if isPointOnEdge(vX, vC, vD):
152
oe[edge_count].vertices = [vert_count, vert_count+3]
153
oe[edge_count+1].vertices = [vert_count, vert_count+4]
154
# find which of A and B is farthest away from X
155
if mDist(vB, vX) > mDist(vA, vX):
156
oe[edge_count+2].vertices = [vert_count, vert_count+1]
157
oe[edge_count+3].vertices = [vert_count+1, vert_count+2]
158
if mDist(vA, vX) > mDist(vB, vX):
159
oe[edge_count+2].vertices = [vert_count, vert_count+2]
160
oe[edge_count+3].vertices = [vert_count+1, vert_count+2]
163
# ProjectGeometry is used to extend two edges to their intersection point.
164
def ProjectGeometry(vX, opoint):
166
def return_distance_checked(X, A, B):
169
point_choice = min(dist1, dist2)
170
if point_choice == dist1: return A, B
173
(o, vert_count, edge_count) = GetActiveObject()
174
vA, vB = return_distance_checked(vX, Vector((opoint[0][0])), Vector((opoint[0][1])))
175
vC, vD = return_distance_checked(vX, Vector((opoint[1][0])), Vector((opoint[1][1])))
176
AddVertsToObject(vert_count, o, vX, vA, vB, vC, vD)
180
oe[edge_count].vertices = [vert_count, vert_count+1]
181
oe[edge_count+1].vertices = [vert_count, vert_count+3]
182
oe[edge_count+2].vertices = [vert_count+1, vert_count+2]
183
oe[edge_count+3].vertices = [vert_count+3, vert_count+4]
186
def getMeshMatrix(obj):
187
is_editmode = (obj.mode == 'EDIT')
189
bpy.ops.object.mode_set(mode='OBJECT')
191
(edges, meshMatrix) = ([],[])
193
verts = mesh.vertices
199
for edge_to_test in edges:
200
p1 = verts[edge_to_test.vertices[0]].co
201
p2 = verts[edge_to_test.vertices[1]].co
202
meshMatrix.append([Vector(p1),Vector(p2)])
208
def getVertEdgeCount():
209
bpy.ops.object.mode_set(mode='OBJECT')
210
vert_count = bpy.context.active_object.data.vertices
211
edge_count = bpy.context.active_object.data.edges
212
return (vert_count, edge_count)
216
bpy.ops.object.mode_set(mode='EDIT')
217
bpy.ops.mesh.select_all(action='TOGGLE')
218
bpy.ops.mesh.select_all(action='TOGGLE')
219
bpy.ops.mesh.remove_doubles(mergedist=VTX_PRECISION)
220
bpy.ops.mesh.select_all(action='TOGGLE') #unselect all
223
def initScriptV(context, self):
224
obj = bpy.context.active_object
225
meshMatrix = getMeshMatrix(obj)
226
(vert_count, edge_count) = getVertEdgeCount()
228
#need 2 edges to be of any use.
229
if len(meshMatrix) < 2:
230
print(str(len(meshMatrix)) +" select, make sure (only) 2 are selected")
233
#dont go any further if the verts are not coplanar
234
if checkIsMatrixCoplanar(meshMatrix): print("seems within tolerance, proceed")
236
print("check your geometry, or decrease tolerance value")
239
# if we reach this point, the edges are coplanar
241
bpy.ops.object.mode_set(mode='EDIT')
242
vSel = bpy.context.active_object.data.total_vert_sel
244
if checkEdges(meshMatrix, obj) == None: print("lines dont intersect")
246
count = CountPointOnEdges(checkEdges(meshMatrix, obj), meshMatrix)
248
ProjectGeometry(checkEdges(meshMatrix, obj), meshMatrix)
251
print("The intersection seems to lie on 1 or 2 edges already")
254
def initScriptT(context, self):
255
obj = bpy.context.active_object
256
meshMatrix = getMeshMatrix(obj)
258
bpy.ops.object.mode_set(mode='EDIT')
259
vSel = bpy.context.active_object.data.total_vert_sel
261
if len(meshMatrix) != 2:
262
print(str(len(meshMatrix)) +" select 2 edges")
264
count = CountPointOnEdges(checkEdges(meshMatrix, obj), meshMatrix)
266
print("Good, Intersection point lies on one of the two edges!")
267
ExtendEdge(checkEdges(meshMatrix, obj), meshMatrix, count)
268
runCleanUp() #neutral function, for removing potential doubles
270
print("Intersection point not on chosen edges")
273
def initScriptX(context, self):
274
obj = bpy.context.active_object
275
meshMatrix = getMeshMatrix(obj)
277
bpy.ops.object.mode_set(mode='EDIT')
279
if len(meshMatrix) != 2:
280
print(str(len(meshMatrix)) +" select, make sure (only) 2 are selected")
282
if checkEdges(meshMatrix, obj) == None:
283
print("lines dont intersect")
285
count = CountPointOnEdges(checkEdges(meshMatrix, obj), meshMatrix)
287
makeGeometryWeld(checkEdges(meshMatrix, obj), meshMatrix)
291
class EdgeIntersections(bpy.types.Operator):
292
'''Makes a weld/slice/extend to intersecting edges/lines'''
293
bl_idname = 'mesh.intersections'
294
bl_label = 'Edge tools : tinyCAD VTX'
295
# bl_options = {'REGISTER', 'UNDO'}
297
mode = bpy.props.IntProperty(name = "Mode",
298
description = "switch between intersection modes",
302
def poll(self, context):
303
obj = context.active_object
304
return obj != None and obj.type == 'MESH'
306
def execute(self, context):
308
initScriptV(context, self)
310
initScriptT(context, self)
312
initScriptX(context, self)
314
print("something undefined happened, send me a test case!")
318
def menu_func(self, context):
319
self.layout.operator(EdgeIntersections.bl_idname, text="Edges V Intersection").mode = -1
320
self.layout.operator(EdgeIntersections.bl_idname, text="Edges T Intersection").mode = 0
321
self.layout.operator(EdgeIntersections.bl_idname, text="Edges X Intersection").mode = 1
324
bpy.utils.register_class(EdgeIntersections)
325
bpy.types.VIEW3D_MT_edit_mesh_specials.append(menu_func)
328
bpy.utils.unregister_class(EdgeIntersections)
329
bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func)
331
if __name__ == "__main__":