Monday, 27 September 2010

blender 2.54 Mesh Tools 2D Boolean on Edges (part 4)

the code is complete for basic intersection cutting, also extends non parallel edges to their extrapolated intersection point


# written by Dealga McArdle
# parts based on Keith (Wahooney) Boshoff, cursor to intersection script and
# Paul Bourke's Shortest Line Between 2 lines
# PKHG from blenderartists.org suggested a few refinements in the Vector code,
# making the code a bit easier to read.
# ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****


import bpy
import sys
from mathutils import Vector


__author__ = ['zeffii']
__version__ = '0.1'
__bpydoc__ = """
"""


bl_addon_info = {
'name': 'Mesh: Slice Intersect',
'author': 'zeffii',
'version': '0.1',
'blender': (2, 5, 4),
'location': 'View3D > EditMode > Specials',
'wiki_url': '',
'category': 'Mesh'}



##function lifted from 3dview_spacebar_menu
def LineLineIntersect (p1, p2, p3, p4):
# based on Paul Bourke's Shortest Line Between 2 lines

min = 0.0000001
v1 = p1 - p3 #Vector((p1.x - p3.x, p1.y - p3.y, p1.z - p3.z))
v2 = p4 - p3 #Vector((p4.x - p3.x, p4.y - p3.y, p4.z - p3.z))

if abs(v2.x) < min and abs(v2.y) < min and abs(v2.z) < min:
return None

v3 = p2 - p1

if abs(v3.x) < min and abs(v3.y) < min and abs(v3.z) < min:
return None

d1 = v1.dot(v2)
d2 = v2.dot(v3)
d3 = v1.dot(v3)
d4 = v2.dot(v2)
d5 = v3.dot(v3)
d = d5 * d4 - d2 * d2

if abs(d) < min:
return None

n = d1 * d2 - d3 * d4
mua = n / d
mub = (d1 + d2 * (mua)) / d4

#modified for clarity, and to use some neat Vector functionality.
return [p1 + mua * v3 , p3 + mub * v2]



##function partially lifted from 3dview_spacebar_menu
def checkEdges(Edge, obj):

p1 = Vector((Edge[0][0]))
p2 = Vector((Edge[0][1]))
p3 = Vector((Edge[1][0]))
p4 = Vector((Edge[1][1]))

line = LineLineIntersect(p1, p2, p3, p4)

if line == None: return None

tm = obj.matrix_world.copy()
point = ((line[0] + line[1]) / 2)
point = tm * point

return point


def makeGeometry(point,outer_points):

# print("start cutting")
# print("intersection point: " + str(point))

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.delete(type='EDGE') # removes edges + verts

(vert_count, edge_count) = getVertEdgeCount()
vert_count = len(vert_count)
edge_count = len(edge_count)

'''
print("vert count is now: " + str(vert_count))
print("Edge count is now: " + str(edge_count))

print(outer_points[0][0])
print(outer_points[0][1])
print(outer_points[1][0])
print(outer_points[1][1])

'''

bpy.ops.object.mode_set(mode='OBJECT') # to be sure.
o = bpy.context.active_object

va = Vector((outer_points[0][0]))
vb = point
vc = Vector((outer_points[0][1]))
vd = Vector((outer_points[1][0]))
ve = Vector((outer_points[1][1]))

o.data.vertices.add(5)
o.data.vertices[vert_count].co = va
o.data.vertices[vert_count+1].co = vb
o.data.vertices[vert_count+2].co = vc
o.data.vertices[vert_count+3].co = vd
o.data.vertices[vert_count+4].co = ve

oe = o.data.edges

oe.add(4)
oe[edge_count].vertices = [vert_count,vert_count+1]
oe[edge_count+1].vertices = [vert_count+2,vert_count+1]
oe[edge_count+2].vertices = [vert_count+3,vert_count+1]
oe[edge_count+3].vertices = [vert_count+4,vert_count+1]


def runCleanUp():

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.remove_doubles(limit=0.0001)

#unselect all
bpy.ops.mesh.select_all(action='TOGGLE')

def getMeshMatrix(obj):

is_editmode = (obj.mode == 'EDIT')
if is_editmode:
bpy.ops.object.mode_set(mode='OBJECT')

edges = []
mesh = obj.data
verts = mesh.vertices

for e in mesh.edges:
if e.select:
edges.append(e)

meshMatrix = []

edgenum = 0
for edge_to_test in edges:
p1 = verts[edge_to_test.vertices[0]].co
p2 = verts[edge_to_test.vertices[1]].co
meshMatrix.append([(p1.x,p1.y,p1.z),(p2.x,p2.y,p2.z)])
edgenum += 1

return meshMatrix


def getVertEdgeCount():

bpy.ops.object.mode_set(mode='OBJECT')

vert_count = bpy.context.active_object.data.vertices
edge_count = bpy.context.active_object.data.edges

return (vert_count, edge_count)

def selectNewGeometry():

# potentially this is where i maybe go back to edit mode and select
# the newly added edges.

return


def initScript(context, self):

obj = bpy.context.active_object

meshMatrix = getMeshMatrix(obj)
(vert_count, edge_count) = getVertEdgeCount()

## force edit mode
bpy.ops.object.mode_set(mode='EDIT')
vSel = bpy.context.active_object.data.total_vert_sel
xv = len(vert_count)
xe = len(edge_count)


if len(meshMatrix) != 2:
print(str(len(meshMatrix)) +" select, make sure (only) 2 are selected")
else:

''' debug prints
print("begin--------------------")
print("the object has " + str(xv) + " verts")
print("currently " + str(vSel) + " verts are selected")
print("the object has " + str(xe) + " edges")
print("currently " + str(len(meshMatrix)) + " edges are selected")
print("--------------------end")
'''

if checkEdges(meshMatrix, obj) == None:
print("lines dont intersect")
else:
makeGeometry(checkEdges(meshMatrix, obj),meshMatrix)
runCleanUp()
# selectNewGeometry()



class SliceIntersectingEdges(bpy.types.Operator):
'''Finds visible intersection, existing or projected through extrapolation, creates new edges'''

bl_idname = "Slice at Intersection"
bl_label = "Slice Intersect"

@classmethod
def poll(self, context):
obj = context.active_object
return obj != None and obj.type == 'MESH'

def execute(self, context):
initScript(context, self)
return {'FINISHED'}


menu_func = (lambda self,
context: self.layout.operator(SliceIntersectingEdges.bl_idname,
text="Slice at Edge Intersection"))

def register():
bpy.types.VIEW3D_MT_edit_mesh_specials.append(menu_func)

def unregister():
bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func)

if __name__ == "__main__":
register()

Saturday, 25 September 2010

blender 2.54 Mesh Tools 2D Boolean on Edges (part 3)

I completed, and shuffled some functions around, can do some brief refactoring.

btw if you dont know what it does. get a mesh, select two intersecting edges, then run this script and look at what happened to the intersection point.




import bpy
import sys
from mathutils import Vector


##function lifted from 3dview_spacebar_menu
def LineLineIntersect (p1, p2, p3, p4):
# based on Paul Bourke's Shortest Line Between 2 lines

min = 0.0000001
v1 = p1 - p3 #Vector((p1.x - p3.x, p1.y - p3.y, p1.z - p3.z))
v2 = p4 - p3 #Vector((p4.x - p3.x, p4.y - p3.y, p4.z - p3.z))

if abs(v2.x) < min and abs(v2.y) < min and abs(v2.z) < min:
return None

v3 = p2 - p1

if abs(v3.x) < min and abs(v3.y) < min and abs(v3.z) < min:
return None

d1 = v1.dot(v2)
d2 = v2.dot(v3)
d3 = v1.dot(v3)
d4 = v2.dot(v2)
d5 = v3.dot(v3)
d = d5 * d4 - d2 * d2

if abs(d) < min:
return None

n = d1 * d2 - d3 * d4
mua = n / d
mub = (d1 + d2 * (mua)) / d4

#modified for clarity, and to use some neat Vector functionality.
return [p1 + mua * v3 , p3 + mub * v2]



##function partially lifted from 3dview_spacebar_menu
def checkEdges(Edge, obj):

p1 = Vector((Edge[0][0]))
p2 = Vector((Edge[0][1]))
p3 = Vector((Edge[1][0]))
p4 = Vector((Edge[1][1]))

line = LineLineIntersect(p1, p2, p3, p4)

if line == None: return None

tm = obj.matrix_world.copy()
point = ((line[0] + line[1]) / 2)
point = tm * point

return point


def makeGeometry(point,outer_points):

# print("start cutting")
# print("intersection point: " + str(point))

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.delete(type='EDGE') # removes edges + verts

(vert_count, edge_count) = getVertEdgeCount()
vert_count = len(vert_count)
edge_count = len(edge_count)

'''
print("vert count is now: " + str(vert_count))
print("Edge count is now: " + str(edge_count))

print(outer_points[0][0])
print(outer_points[0][1])
print(outer_points[1][0])
print(outer_points[1][1])

'''

bpy.ops.object.mode_set(mode='OBJECT') # to be sure.
o = bpy.context.active_object

va = Vector((outer_points[0][0]))
vb = point
vc = Vector((outer_points[0][1]))
vd = Vector((outer_points[1][0]))
ve = Vector((outer_points[1][1]))

o.data.vertices.add(5)
o.data.vertices[vert_count].co = va
o.data.vertices[vert_count+1].co = vb
o.data.vertices[vert_count+2].co = vc
o.data.vertices[vert_count+3].co = vd
o.data.vertices[vert_count+4].co = ve

oe = o.data.edges

oe.add(4)
oe[edge_count].vertices = [vert_count,vert_count+1]
oe[edge_count+1].vertices = [vert_count+2,vert_count+1]
oe[edge_count+2].vertices = [vert_count+3,vert_count+1]
oe[edge_count+3].vertices = [vert_count+4,vert_count+1]


def runCleanUp():

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.remove_doubles(limit=0.0001)


def getMeshMatrix(obj):

is_editmode = (obj.mode == 'EDIT')
if is_editmode:
bpy.ops.object.mode_set(mode='OBJECT')

edges = []
mesh = obj.data
verts = mesh.vertices

for e in mesh.edges:
if e.select:
edges.append(e)

meshMatrix = []

edgenum = 0
for edge_to_test in edges:
p1 = verts[edge_to_test.vertices[0]].co
p2 = verts[edge_to_test.vertices[1]].co
meshMatrix.append([(p1.x,p1.y,p1.z),(p2.x,p2.y,p2.z)])
edgenum += 1

return meshMatrix


def getVertEdgeCount():

bpy.ops.object.mode_set(mode='OBJECT')

vert_count = bpy.context.active_object.data.vertices
edge_count = bpy.context.active_object.data.edges

return (vert_count, edge_count)

def selectNewGeometry():

# potentially this is where i maybe go back to edit mode and select
# the newly added edges.

return


def initScript():

obj = bpy.context.active_object

meshMatrix = getMeshMatrix(obj)
(vert_count, edge_count) = getVertEdgeCount()

## force edit mode
bpy.ops.object.mode_set(mode='EDIT')
vSel = bpy.context.active_object.data.total_vert_sel
xv = len(vert_count)
xe = len(edge_count)


if len(meshMatrix) != 2:
print(str(len(meshMatrix)) +" select, make sure (only) 2 are selected")
else:

''' debug prints
print("begin--------------------")
print("the object has " + str(xv) + " verts")
print("currently " + str(vSel) + " verts are selected")
print("the object has " + str(xe) + " edges")
print("currently " + str(len(meshMatrix)) + " edges are selected")
print("--------------------end")
'''

if checkEdges(meshMatrix, obj) == None:
print("lines dont intersect")
else:
makeGeometry(checkEdges(meshMatrix, obj),meshMatrix)
runCleanUp()
# selectNewGeometry()


# runs the joint
initScript()



Sunday, 19 September 2010

blender 2.54 Mesh Tools 2D Boolean on Edges (part 2)

this i think represents the most complicated case over which the code should eventually be able to iterate.

it should manipulate the given geometry into this:

blender 2.54 Mesh Tools 2D Boolean on Edges (part 1)

While doing typography design in blender i was faced repeatedly with some very basic but tedious and time consuming operations. Instead of waiting for the pre 2.5 CAD tools to be updated to 2.5x I started coding some of the prerequisites.

here is what i put into blender console to get the selected edges of the current
object. ( tho, after selecting the edges you must enter object mode with the object still selected for this script to be able to get the information )


import sys

obj = bpy.context.active_object
edges = []
mesh = obj.data
verts = mesh.vertices

# this for loop is from the 3dview_spacebar_menu
for e in mesh.edges:
if e.select:
edges.append(e)

meshMatrix = []
edgenum = 0
for edge_to_test in edges:
p1 = verts[edge_to_test.vertices[0]].co
p2 = verts[edge_to_test.vertices[1]].co
meshMatrix.append([(p1.x,p1.y,p1.z),(p2.x,p2.y,p2.z)])
edgenum += 1

for edge_ in meshMatrix:
edge_



this spits out vertex data like :


[(0.0, 1.0, 1.0), (5.211963127749186e-08, 0.0, 1.0)]
[(5.211963127749186e-08, 0.0, 1.0), (5.211963127749186e-08, 0.0, 2.0)]
[(0.6621248126029968, 0.0, 2.0), (0.6621248126029968, 0.0, 1.0)]
[(0.6621248126029968, 0.0, 1.0), (0.6621248126029968, 1.637786626815796, 1.0)]
[(0.6621248126029968, 1.637786626815796, -0.04176926612854004), (0.6621248126029968, 1.637786626815796, 1.0)]
[(2.0378873348236084, 1.637786626815796, 1.0), (2.0378873348236084, 1.637786626815796, -0.04176926612854004)]
[(2.0378873348236084, 1.637786626815796, 1.0), (2.0378873348236084, 0.0, 1.0)]


the previous code snippet needs commas to seperate the list. This is purposely coded verbosely with many print statements while i'm bug testing my logic :) optimizing will happen when i'm confident this catches undesirable anomalies.



# test
import sys
import math

preStantonList = [
[(0.0, 1.0, 1.0), (5.211963127749186e-08, 0.0, 1.0)],
[(5.211963127749186e-08, 0.0, 1.0), (5.211963127749186e-08, 0.0, 2.0)],
[(0.6621248126029968, 0.0, 2.0), (0.6621248126029968, 0.0, 1.0)],
[(0.6621248126029968, 0.0, 1.0), (0.6621248126029968, 1.637786626815796, 1.0)],
[(0.6621248126029968, 1.637786626815796, -0.04176926612854004), (0.6621248126029968, 1.637786626815796, 1.0)],
[(2.0378873348236084, 1.637786626815796, 1.0), (2.0378873348236084, 1.637786626815796, -0.04176926612854004)],
[(2.0378873348236084, 1.637786626815796, 1.0), (2.0378873348236084, 0.0, 1.0)]

]

def calcDifference(val1, val2):

if val1 > val2: return val1-val2
if val2 > val1: return val2-val1
if val1 == val2: return 0.0


def isListHomogenous(vertList):
'''
function returns True if no anomalies are found, False if one vert exceeds epsilon
'''

def average(vertList):
sum = 0.0
for i in vertList:
sum += i

avsum = sum / len(vertList)
return avsum

def isEpsilon(val1, val2):
min = 0.0000001

if calcDifference(val1, val2) == 0.0: return True
if calcDifference(val1, val2) < min: return True
return False

def checkList(vertList):

print average(vertList)
for i in vertList:
if not isEpsilon(i, average(vertList)): return False

# reaches this stage, means the variation is probably minimal
return True

#my Boolean
GOODTOGO = False
GOODTOGO = checkList(vertList)

return GOODTOGO



def printEdgeList(eList):

(p1x, p1y, p1z) = (0.0, 0.0, 0.0)
(p2x, p2y, p2z) = (0.0, 0.0, 0.0)

planarX = 0.0
planarY = 0.0
planarZ = 0.0

edgenum = 0

vertXList = []
vertYList = []
vertZList = []

xPlaneVerts = []
yPlaneVerts = []
zPlaneVerts = []

def sumList(coordinate_list):
summing = 0.0
for i in coordinate_list:
summing += i

return summing


def VertsDeltaInPlane(axis, value1, value2):
min = 0.0000001

difference = calcDifference(value1, value2)

# gets dirty for larger numbers, but we aren't doing intergalatic travel.
if difference < min: difference = 0.0
print "difference in axis " + axis + " component of this edge is " + str(difference)

return difference


def printCoordinate(val_x,val_y,val_z):
print "| x = " + str(val_x),
print "| y = " + str(val_y),
print "| z = " + str(val_z)


for item in eList:
print "=========="
print "edge [" + str(edgenum) + "] .....(" + str(edgenum+1) + ")"
print "vertex 0",
p1x = item[0][0]
p1y = item[0][1]
p1z = item[0][2]
printCoordinate(p1x,p1y,p1z)
print "vertex 1",
p2x = item[1][0]
p2y = item[1][1]
p2z = item[1][2]
printCoordinate(p2x,p2y,p2z)
planarX = VertsDeltaInPlane("x", p1x, p2x)
planarY = VertsDeltaInPlane("y", p1y, p2y)
planarZ = VertsDeltaInPlane("z", p1z, p2z)

# collects all vertices
xComp = [p1x,p2x]
yComp = [p1y,p2y]
zComp = [p1z,p2z]
xPlaneVerts.extend(xComp)
yPlaneVerts.extend(yComp)
zPlaneVerts.extend(zComp)

vertXList.append(planarX)
vertYList.append(planarY)
vertZList.append(planarZ)

edgenum += 1

def myRound(value):
return ".7f" % value


print "-------conclusion---------"
# this statement checks if the the verts on each edge or on the same plane
# check here if the selection is usable or not.

if sumList(vertXList) == 0.0:
print "x", xPlaneVerts
if isListHomogenous(xPlaneVerts):
print "GOOD TO GO!",
print "the plane is X at " + str(round(p1x,6))
else:
print "same plane but not same elevation"
elif sumList(vertYList) == 0.0:
print "y", yPlaneVerts
if isListHomogenous(yPlaneVerts):
print "GOOD TO GO!",
print "the plane is Y at " + str(round(p1y,6))
else:
print "same plane but not same elevation"
elif sumList(vertZList) == 0.0:
print "z", zPlaneVerts
if isListHomogenous(zPlaneVerts):
print "GOOD TO GO!",
print "the plane is Z at " + str(round(p1z,6))
else:
print "same plane but not same elevation"
else:
print "not on the same plane, the script will not proceed"

printEdgeList(preStantonList)