forked from bartvdbraak/blender
2a013c1b5b
Verts being set to NAN and cursor being placed as well as painting.
923 lines
30 KiB
Python
923 lines
30 KiB
Python
# SPACEHANDLER.VIEW3D.EVENT
|
|
# Dont run, event handelers are accessed in the from the 3d View menu.
|
|
|
|
import Blender
|
|
from Blender import Mathutils, Window, Scene, Draw, Mesh, NMesh
|
|
from Blender.Mathutils import CrossVecs, Matrix, Vector, Intersect, LineIntersect
|
|
|
|
|
|
# DESCRIPTION:
|
|
# screen_x, screen_y the origin point of the pick ray
|
|
# it is either the mouse location
|
|
# localMatrix is used if you want to have the returned values in an objects localspace.
|
|
# this is usefull when dealing with an objects data such as verts.
|
|
# or if useMid is true, the midpoint of the current 3dview
|
|
# returns
|
|
# Origin - the origin point of the pick ray
|
|
# Direction - the direction vector of the pick ray
|
|
# in global coordinates
|
|
epsilon = 1e-3 # just a small value to account for floating point errors
|
|
|
|
def getPickRay(screen_x, screen_y, localMatrix=None, useMid = False):
|
|
|
|
# Constant function variables
|
|
p = getPickRay.p
|
|
d = getPickRay.d
|
|
|
|
for win3d in Window.GetScreenInfo(Window.Types.VIEW3D): # we search all 3dwins for the one containing the point (screen_x, screen_y) (could be the mousecoords for example)
|
|
win_min_x, win_min_y, win_max_x, win_max_y = win3d['vertices']
|
|
# calculate a few geometric extents for this window
|
|
|
|
win_mid_x = (win_max_x + win_min_x + 1.0) * 0.5
|
|
win_mid_y = (win_max_y + win_min_y + 1.0) * 0.5
|
|
win_size_x = (win_max_x - win_min_x + 1.0) * 0.5
|
|
win_size_y = (win_max_y - win_min_y + 1.0) * 0.5
|
|
|
|
#useMid is for projecting the coordinates when we subdivide the screen into bins
|
|
if useMid: # == True
|
|
screen_x = win_mid_x
|
|
screen_y = win_mid_y
|
|
|
|
# if the given screencoords (screen_x, screen_y) are within the 3dwin we fount the right one...
|
|
if (win_max_x > screen_x > win_min_x) and ( win_max_y > screen_y > win_min_y):
|
|
# first we handle all pending events for this window (otherwise the matrices might come out wrong)
|
|
Window.QHandle(win3d['id'])
|
|
|
|
# now we get a few matrices for our window...
|
|
# sorry - i cannot explain here what they all do
|
|
# - if you're not familiar with all those matrices take a look at an introduction to OpenGL...
|
|
pm = Window.GetPerspMatrix() # the prespective matrix
|
|
pmi = Matrix(pm); pmi.invert() # the inverted perspective matrix
|
|
|
|
if (1.0 - epsilon < pmi[3][3] < 1.0 + epsilon):
|
|
# pmi[3][3] is 1.0 if the 3dwin is in ortho-projection mode (toggled with numpad 5)
|
|
hms = getPickRay.hms
|
|
ortho_d = getPickRay.ortho_d
|
|
|
|
# ortho mode: is a bit strange - actually there's no definite location of the camera ...
|
|
# but the camera could be displaced anywhere along the viewing direction.
|
|
|
|
ortho_d.x, ortho_d.y, ortho_d.z = Window.GetViewVector()
|
|
ortho_d.w = 0
|
|
|
|
# all rays are parallel in ortho mode - so the direction vector is simply the viewing direction
|
|
#hms.x, hms.y, hms.z, hms.w = (screen_x-win_mid_x) /win_size_x, (screen_y-win_mid_y) / win_size_y, 0.0, 1.0
|
|
hms[:] = (screen_x-win_mid_x) /win_size_x, (screen_y-win_mid_y) / win_size_y, 0.0, 1.0
|
|
|
|
# these are the homogenious screencoords of the point (screen_x, screen_y) ranging from -1 to +1
|
|
p=(hms*pmi) + (1000*ortho_d)
|
|
p.resize3D()
|
|
d[:] = ortho_d[:3]
|
|
|
|
|
|
# Finally we shift the position infinitely far away in
|
|
# the viewing direction to make sure the camera if outside the scene
|
|
# (this is actually a hack because this function
|
|
# is used in sculpt_mesh to initialize backface culling...)
|
|
else:
|
|
# PERSPECTIVE MODE: here everything is well defined - all rays converge at the camera's location
|
|
vmi = Matrix(Window.GetViewMatrix()); vmi.invert() # the inverse viewing matrix
|
|
fp = getPickRay.fp
|
|
|
|
dx = pm[3][3] * (((screen_x-win_min_x)/win_size_x)-1.0) - pm[3][0]
|
|
dy = pm[3][3] * (((screen_y-win_min_y)/win_size_y)-1.0) - pm[3][1]
|
|
|
|
fp[:] = \
|
|
pmi[0][0]*dx+pmi[1][0]*dy,\
|
|
pmi[0][1]*dx+pmi[1][1]*dy,\
|
|
pmi[0][2]*dx+pmi[1][2]*dy
|
|
|
|
# fp is a global 3dpoint obtained from "unprojecting" the screenspace-point (screen_x, screen_y)
|
|
#- figuring out how to calculate this took me quite some time.
|
|
# The calculation of dxy and fp are simplified versions of my original code
|
|
#- so it's almost impossible to explain what's going on geometrically... sorry
|
|
|
|
p[:] = vmi[3][:3]
|
|
|
|
# the camera's location in global 3dcoords can be read directly from the inverted viewmatrix
|
|
#d.x, d.y, d.z =normalize_v3(sub_v3v3(p, fp))
|
|
d[:] = p.x-fp.x, p.y-fp.y, p.z-fp.z
|
|
|
|
#print 'd', d, 'p', p, 'fp', fp
|
|
|
|
|
|
# the direction vector is simply the difference vector from the virtual camera's position
|
|
#to the unprojected (screenspace) point fp
|
|
|
|
# Do we want to return a direction in object's localspace?
|
|
|
|
if localMatrix:
|
|
localInvMatrix = Matrix(localMatrix)
|
|
localInvMatrix.invert()
|
|
p = p*localInvMatrix
|
|
d = d*localInvMatrix # normalize_v3
|
|
p.x += localInvMatrix[3][0]
|
|
p.y += localInvMatrix[3][1]
|
|
p.z += localInvMatrix[3][2]
|
|
|
|
#else: # Worldspace, do nothing
|
|
|
|
d.normalize()
|
|
return True, p, d # Origin, Direction
|
|
|
|
# Mouse is not in any view, return None.
|
|
return False, None, None
|
|
|
|
# Constant function variables
|
|
getPickRay.d = Vector(0,0,0) # Perspective, 3d
|
|
getPickRay.p = Vector(0,0,0)
|
|
getPickRay.fp = Vector(0,0,0)
|
|
|
|
getPickRay.hms = Vector(0,0,0,0) # ortho only 4d
|
|
getPickRay.ortho_d = Vector(0,0,0,0) # ortho only 4d
|
|
|
|
|
|
|
|
def ui_set_preferences(user_interface=1):
|
|
# Create data and set defaults.
|
|
ADAPTIVE_GEOMETRY_but = Draw.Create(0)
|
|
BRUSH_MODE_but = Draw.Create(1)
|
|
BRUSH_PRESSURE_but = Draw.Create(0.05)
|
|
BRUSH_RADIUS_but = Draw.Create(0.25)
|
|
RESOLUTION_MIN_but = Draw.Create(0.1)
|
|
DISPLACE_NORMAL_MODE_but = Draw.Create(2)
|
|
STATIC_NORMAL_but = Draw.Create(1)
|
|
XPLANE_CLIP_but = Draw.Create(0)
|
|
STATIC_MESH_but = Draw.Create(1)
|
|
FIX_TOPOLOGY_but = Draw.Create(0)
|
|
|
|
# Remember old variables if alredy set.
|
|
try:
|
|
ADAPTIVE_GEOMETRY_but.val = Blender.bbrush['ADAPTIVE_GEOMETRY']
|
|
BRUSH_MODE_but.val = Blender.bbrush['BRUSH_MODE']
|
|
BRUSH_PRESSURE_but.val = Blender.bbrush['BRUSH_PRESSURE']
|
|
BRUSH_RADIUS_but.val = Blender.bbrush['BRUSH_RADIUS']
|
|
RESOLUTION_MIN_but.val = Blender.bbrush['RESOLUTION_MIN']
|
|
DISPLACE_NORMAL_MODE_but.val = Blender.bbrush['DISPLACE_NORMAL_MODE']
|
|
STATIC_NORMAL_but.val = Blender.bbrush['STATIC_NORMAL']
|
|
XPLANE_CLIP_but.val = Blender.bbrush['XPLANE_CLIP']
|
|
STATIC_MESH_but.val = Blender.bbrush['STATIC_MESH']
|
|
FIX_TOPOLOGY_but.val = Blender.bbrush['FIX_TOPOLOGY']
|
|
except:
|
|
Blender.bbrush = {}
|
|
|
|
if user_interface:
|
|
pup_block = [\
|
|
'Brush Options',\
|
|
('Adaptive Geometry', ADAPTIVE_GEOMETRY_but, 'Add and remove detail as needed. Uses min/max resolution.'),\
|
|
('Brush Type: ', BRUSH_MODE_but, 1, 5, 'Push/Pull:1, Grow/Shrink:2, Spin:3, Relax:4, Goo:5'),\
|
|
('Pressure: ', BRUSH_PRESSURE_but, 0.0, 1.0, 'Pressure of the brush.'),\
|
|
('Size: ', BRUSH_RADIUS_but, 0.01, 2.0, 'Size of the brush.'),\
|
|
('Geometry Res: ', RESOLUTION_MIN_but, 0.01, 0.5, 'Size of the brush & Adaptive Subdivision.'),\
|
|
('Displace Vector: ', DISPLACE_NORMAL_MODE_but, 1, 4, 'Vertex Normal:1, Median Normal:2, Face Normal:3, View Normal:4'),\
|
|
('Static Normal', STATIC_NORMAL_but, 'Use the initial normal only.'),\
|
|
('No X Crossing', XPLANE_CLIP_but, 'Dont allow verts to have a negative X axis (use for x-mirror).'),\
|
|
('Static Mesh', STATIC_MESH_but, 'During mouse interaction, dont update the mesh.'),\
|
|
#('Fix Topology', FIX_TOPOLOGY_but, 'Fix the mesh structure by rotating edges '),\
|
|
]
|
|
|
|
Draw.PupBlock('BlenBrush Prefs (RMB)', pup_block)
|
|
|
|
Blender.bbrush['ADAPTIVE_GEOMETRY'] = ADAPTIVE_GEOMETRY_but.val
|
|
Blender.bbrush['BRUSH_MODE'] = BRUSH_MODE_but.val
|
|
Blender.bbrush['BRUSH_PRESSURE'] = BRUSH_PRESSURE_but.val
|
|
Blender.bbrush['BRUSH_RADIUS'] = BRUSH_RADIUS_but.val
|
|
Blender.bbrush['RESOLUTION_MIN'] = RESOLUTION_MIN_but.val
|
|
Blender.bbrush['DISPLACE_NORMAL_MODE'] = DISPLACE_NORMAL_MODE_but.val
|
|
Blender.bbrush['STATIC_NORMAL'] = STATIC_NORMAL_but.val
|
|
Blender.bbrush['XPLANE_CLIP'] = XPLANE_CLIP_but.val
|
|
Blender.bbrush['STATIC_MESH'] = STATIC_MESH_but.val
|
|
Blender.bbrush['FIX_TOPOLOGY'] = FIX_TOPOLOGY_but.val
|
|
|
|
|
|
def triangulateNMesh(nm):
|
|
'''
|
|
Converts the meshes faces to tris, modifies the mesh in place.
|
|
'''
|
|
|
|
#============================================================================#
|
|
# Returns a new face that has the same properties as the origional face #
|
|
# but with no verts #
|
|
#============================================================================#
|
|
def copyFace(face):
|
|
newFace = NMesh.Face()
|
|
# Copy some generic properties
|
|
newFace.mode = face.mode
|
|
if face.image != None:
|
|
newFace.image = face.image
|
|
newFace.flag = face.flag
|
|
newFace.mat = face.mat
|
|
newFace.smooth = face.smooth
|
|
return newFace
|
|
|
|
# 2 List comprehensions are a lot faster then 1 for loop.
|
|
tris = [f for f in nm.faces if len(f) == 3]
|
|
quads = [f for f in nm.faces if len(f) == 4]
|
|
|
|
|
|
if quads: # Mesh may have no quads.
|
|
has_uv = quads[0].uv
|
|
has_vcol = quads[0].col
|
|
for quadFace in quads:
|
|
# Triangulate along the shortest edge
|
|
#if (quadFace.v[0].co - quadFace.v[2].co).length < (quadFace.v[1].co - quadFace.v[3].co).length:
|
|
a1 = Mathutils.TriangleArea(quadFace.v[0].co, quadFace.v[1].co, quadFace.v[2].co)
|
|
a2 = Mathutils.TriangleArea(quadFace.v[0].co, quadFace.v[2].co, quadFace.v[3].co)
|
|
b1 = Mathutils.TriangleArea(quadFace.v[1].co, quadFace.v[2].co, quadFace.v[3].co)
|
|
b2 = Mathutils.TriangleArea(quadFace.v[1].co, quadFace.v[3].co, quadFace.v[0].co)
|
|
a1,a2 = min(a1, a2), max(a1, a2)
|
|
b1,b2 = min(b1, b2), max(b1, b2)
|
|
if a1/a2 < b1/b2:
|
|
|
|
# Method 1
|
|
triA = 0,1,2
|
|
triB = 0,2,3
|
|
else:
|
|
# Method 2
|
|
triA = 0,1,3
|
|
triB = 1,2,3
|
|
|
|
for tri1, tri2, tri3 in (triA, triB):
|
|
newFace = copyFace(quadFace)
|
|
newFace.v = [quadFace.v[tri1], quadFace.v[tri2], quadFace.v[tri3]]
|
|
if has_uv: newFace.uv = [quadFace.uv[tri1], quadFace.uv[tri2], quadFace.uv[tri3]]
|
|
if has_vcol: newFace.col = [quadFace.col[tri1], quadFace.col[tri2], quadFace.col[tri3]]
|
|
|
|
nm.addEdge(quadFace.v[tri1], quadFace.v[tri3]) # Add an edge where the 2 tris are devided.
|
|
tris.append(newFace)
|
|
|
|
nm.faces = tris
|
|
|
|
import mesh_tri2quad
|
|
def fix_topolagy(mesh):
|
|
ob = Scene.GetCurrent().getActiveObject()
|
|
|
|
for f in mesh.faces:
|
|
f.sel = 1
|
|
mesh.quadToTriangle(0)
|
|
nmesh = ob.getData()
|
|
|
|
mesh_tri2quad.tri2quad(nmesh, 100, 0)
|
|
triangulateNMesh(nmesh)
|
|
nmesh.update()
|
|
|
|
mesh = Mesh.Get(mesh.name)
|
|
for f in mesh.faces:
|
|
f.sel=1
|
|
mesh.quadToTriangle()
|
|
Mesh.Mode(Mesh.SelectModes['EDGE'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def event_main():
|
|
#print Blender.event
|
|
#mod =[Window.Qual.CTRL, Window.Qual.ALT, Window.Qual.SHIFT]
|
|
mod =[Window.Qual.CTRL, Window.Qual.ALT]
|
|
|
|
qual = Window.GetKeyQualifiers()
|
|
SHIFT_FLAG = Window.Qual.SHIFT
|
|
CTRL_FLAG = Window.Qual.CTRL
|
|
|
|
|
|
# UNDO
|
|
"""
|
|
is_editmode = Window.EditMode() # Exit Editmode.
|
|
if is_editmode: Window.EditMode(0)
|
|
if Blender.event == Draw.UKEY:
|
|
if is_editmode:
|
|
Blender.event = Draw.UKEY
|
|
return
|
|
else:
|
|
winId = [win3d for win3d in Window.GetScreenInfo(Window.Types.VIEW3D)][0]
|
|
Blender.event = None
|
|
Window.QHandle(winId['id'])
|
|
Window.EditMode(1)
|
|
Window.QHandle(winId['id'])
|
|
Window.QAdd(winId['id'],Draw.UKEY,1) # Change KeyPress Here for EditMode
|
|
Window.QAdd(winId['id'],Draw.UKEY,0)
|
|
Window.QHandle(winId['id'])
|
|
Window.EditMode(0)
|
|
Blender.event = None
|
|
return
|
|
"""
|
|
|
|
ob = Scene.GetCurrent().getActiveObject()
|
|
if not ob or ob.getType() != 'Mesh':
|
|
return
|
|
|
|
# Mouse button down with no modifiers.
|
|
if Blender.event == Draw.LEFTMOUSE and not [True for m in mod if m & qual]:
|
|
# Do not exit (draw)
|
|
pass
|
|
elif Blender.event == Draw.RIGHTMOUSE and not [True for m in mod if m & qual]:
|
|
ui_set_preferences()
|
|
return
|
|
else:
|
|
return
|
|
|
|
del qual
|
|
|
|
|
|
try:
|
|
Blender.bbrush
|
|
except:
|
|
# First time run
|
|
ui_set_preferences() # No ui
|
|
return
|
|
|
|
ADAPTIVE_GEOMETRY = Blender.bbrush['ADAPTIVE_GEOMETRY'] # 1
|
|
BRUSH_MODE = Blender.bbrush['BRUSH_MODE'] # 1
|
|
BRUSH_PRESSURE_ORIG = Blender.bbrush['BRUSH_PRESSURE'] # 0.1
|
|
BRUSH_RADIUS = Blender.bbrush['BRUSH_RADIUS'] # 0.5
|
|
RESOLUTION_MIN = Blender.bbrush['RESOLUTION_MIN'] # 0.08
|
|
STATIC_NORMAL = Blender.bbrush['STATIC_NORMAL'] # 0
|
|
XPLANE_CLIP = Blender.bbrush['XPLANE_CLIP'] # 0
|
|
DISPLACE_NORMAL_MODE = Blender.bbrush['DISPLACE_NORMAL_MODE'] # 'Vertex Normal%x1|Median Normal%x2|Face Normal%x3|View Normal%x4'
|
|
STATIC_MESH = Blender.bbrush['STATIC_MESH']
|
|
FIX_TOPOLOGY = Blender.bbrush['FIX_TOPOLOGY']
|
|
|
|
|
|
# Angle between Vecs wrapper.
|
|
AngleBetweenVecs = Mathutils.AngleBetweenVecs
|
|
def ang(v1,v2):
|
|
try:
|
|
return AngleBetweenVecs(v1,v2)
|
|
except:
|
|
return 180
|
|
"""
|
|
def Angle2D(x1, y1, x2, y2):
|
|
import math
|
|
RAD2DEG = 57.295779513082323
|
|
'''
|
|
Return the angle between two vectors on a plane
|
|
The angle is from vector 1 to vector 2, positive anticlockwise
|
|
The result is between -pi -> pi
|
|
'''
|
|
dtheta = math.atan2(y2,x2) - math.atan2(y1,x1) # theta1 - theta2
|
|
while dtheta > math.pi:
|
|
dtheta -= (math.pi*2)
|
|
while dtheta < -math.pi:
|
|
dtheta += (math.pi*2)
|
|
return dtheta * RAD2DEG #(180.0 / math.pi)
|
|
"""
|
|
|
|
def faceIntersect(f):
|
|
isect = Intersect(f.v[0].co, f.v[1].co, f.v[2].co, Direction, Origin, 1) # Clipped.
|
|
if isect:
|
|
return isect
|
|
elif len(f.v) == 4:
|
|
isect = Intersect(f.v[0].co, f.v[2].co, f.v[3].co, Direction, Origin, 1) # Clipped.
|
|
return isect
|
|
"""
|
|
# Unused so farm, too slow.
|
|
def removeDouble(v1,v2, me):
|
|
v1List = [f for f in me.faces if v1 in f.v]
|
|
v2List = [f for f in me.faces if v2 in f.v]
|
|
#print v1List
|
|
#print v2List
|
|
remFaces = []
|
|
newFaces = []
|
|
for f2 in v2List:
|
|
f2ls = list(f2.v)
|
|
i = f2ls.index(v2)
|
|
f2ls[i] = v1
|
|
#remFaces.append(f2)
|
|
if f2ls.count(v1) == 1:
|
|
newFaces.append(tuple(f2ls))
|
|
if remFaces:
|
|
me.faces.delete(1, remFaces)
|
|
#me.verts.delete(v2)
|
|
if newFaces:
|
|
me.faces.extend(newFaces)
|
|
"""
|
|
|
|
|
|
me = ob.getData(mesh=1)
|
|
|
|
is_editmode = Window.EditMode() # Exit Editmode.
|
|
if is_editmode: Window.EditMode(0)
|
|
|
|
Mesh.Mode(Mesh.SelectModes['EDGE'])
|
|
|
|
# At the moment ADAPTIVE_GEOMETRY is the only thing that uses selection.
|
|
if ADAPTIVE_GEOMETRY:
|
|
# Deslect all
|
|
SEL_FLAG = Mesh.EdgeFlags['SELECT']
|
|
'''
|
|
for ed in me.edges:
|
|
#ed.flag &= ~SEL_FLAG # deselect. 34
|
|
ed.flag = 32
|
|
'''
|
|
#filter(lambda ed: setattr(ed, 'flag', 32), me.edges)
|
|
|
|
'''for v in me.verts:
|
|
v.sel = 0'''
|
|
#filter(lambda v: setattr(v, 'sel', 0), me.verts)
|
|
# DESELECT ABSOLUTLY ALL
|
|
Mesh.Mode(Mesh.SelectModes['FACE'])
|
|
filter(lambda f: setattr(f, 'sel', 0), me.faces)
|
|
|
|
Mesh.Mode(Mesh.SelectModes['EDGE'])
|
|
filter(lambda ed: setattr(ed, 'flag', 32), me.edges)
|
|
|
|
Mesh.Mode(Mesh.SelectModes['VERTEX'])
|
|
filter(lambda v: setattr(v, 'sel', 0), me.verts)
|
|
|
|
Mesh.Mode(Mesh.SelectModes['EDGE'])
|
|
|
|
i = 0
|
|
time = Blender.sys.time()
|
|
last_best_isect = None # used for goo only
|
|
old_screen_x, old_screen_y = 1<<30, 1<<30
|
|
goo_dir_vec = last_goo_dir_vec = gooRotMatrix = None # goo mode only.
|
|
|
|
# Normal stuff
|
|
iFaceNormal = medainNormal = None
|
|
|
|
# Store all vert normals for now.
|
|
if BRUSH_MODE == 1 and STATIC_NORMAL: # Push pull
|
|
vert_orig_normals = dict([(v, v.no) for v in me.verts])
|
|
|
|
elif BRUSH_MODE == 4: # RELAX, BUILD EDGE CONNECTIVITE DATA.
|
|
# we need edge connectivity
|
|
#vertEdgeUsers = [list() for i in xrange(len(me.verts))]
|
|
verts_connected_by_edge = [list() for i in xrange(len(me.verts))]
|
|
|
|
for ed in me.edges:
|
|
i1, i2 = ed.v1.index, ed.v2.index
|
|
#vertEdgeUsers[i1].append(ed)
|
|
#vertEdgeUsers[i2].append(ed)
|
|
|
|
verts_connected_by_edge[i1].append(ed.v2)
|
|
verts_connected_by_edge[i2].append(ed.v1)
|
|
|
|
if STATIC_MESH:
|
|
|
|
# Try and find a static mesh to reuse.
|
|
# this is because we dont want to make a new mesh for each stroke.
|
|
mesh_static = None
|
|
for _me_name_ in Blender.NMesh.GetNames():
|
|
_me_ = Mesh.Get(_me_name_)
|
|
#print _me_.users , len(me.verts)
|
|
if _me_.users == 0 and len(_me_.verts) == 0:
|
|
mesh_static = _me_
|
|
#print 'using', _me_.name
|
|
break
|
|
del _me_name_
|
|
del _me_
|
|
|
|
if not mesh_static:
|
|
mesh_static = Mesh.New()
|
|
print 'Making new mesh', mesh_static.name
|
|
|
|
mesh_static.verts.extend([v.co for v in me.verts])
|
|
mesh_static.faces.extend([tuple([mesh_static.verts[v.index] for v in f.v]) for f in me.faces])
|
|
|
|
|
|
best_isect = gooPlane = None
|
|
|
|
while Window.GetMouseButtons() == 1:
|
|
i+=1
|
|
screen_x, screen_y = Window.GetMouseCoords()
|
|
|
|
# Skip when no mouse movement, Only for Goo!
|
|
if screen_x == old_screen_x and screen_y == old_screen_y:
|
|
if BRUSH_MODE == 5: # Dont modify while mouse is not moved for goo.
|
|
continue
|
|
else: # mouse has moved get the new mouse ray.
|
|
old_screen_x, old_screen_y = screen_x, screen_y
|
|
mouseInView, Origin, Direction = getPickRay(screen_x, screen_y, ob.matrixWorld)
|
|
if not mouseInView or not Origin:
|
|
return
|
|
Origin_SCALE = Origin * 100
|
|
|
|
# Find an intersecting face!
|
|
bestLen = 1<<30 # start with an assumed realy bad match.
|
|
best_isect = None # last intersect is used for goo.
|
|
best_face = None
|
|
|
|
if not last_best_isect:
|
|
last_best_isect = best_isect
|
|
|
|
if not mouseInView:
|
|
last_best_isect = None
|
|
|
|
else:
|
|
# Find Face intersection closest to the view.
|
|
#for f in [f for f in me.faces if ang(f.no, Direction) < 90]:
|
|
|
|
# Goo brush only intersects faces once, after that the brush follows teh view plain.
|
|
if BRUSH_MODE == 5 and gooPlane != None and gooPlane:
|
|
best_isect = Intersect( gooPlane[0], gooPlane[1], gooPlane[2], Direction, Origin, 0) # Non clipped
|
|
else:
|
|
if STATIC_MESH:
|
|
intersectingFaces = [(f, ix) for f in mesh_static.faces for ix in (faceIntersect(f),) if ix]
|
|
else:
|
|
intersectingFaces = [(f, ix) for f in me.faces for ix in (faceIntersect(f),) if ix]
|
|
|
|
for f, isect in intersectingFaces:
|
|
l = (Origin_SCALE-isect).length
|
|
if l < bestLen:
|
|
best_face = f
|
|
best_isect = isect
|
|
bestLen = l
|
|
|
|
if not best_isect:
|
|
# Dont interpolate once the mouse moves off the mesh.
|
|
lastGooVec = last_best_isect = None
|
|
|
|
else: # mouseInView must be true also
|
|
|
|
# Use the shift key to modify the pressure.
|
|
if SHIFT_FLAG & Window.GetKeyQualifiers():
|
|
BRUSH_PRESSURE = -BRUSH_PRESSURE_ORIG
|
|
else:
|
|
BRUSH_PRESSURE = BRUSH_PRESSURE_ORIG
|
|
|
|
brush_verts = [(v,le) for v in me.verts for le in ((v.co-best_isect).length,) if le <= BRUSH_RADIUS]
|
|
|
|
# SETUP ONCE ONLY VARIABLES
|
|
if STATIC_NORMAL: # Only set the normal once.
|
|
if not iFaceNormal:
|
|
iFaceNormal = best_face.no
|
|
else:
|
|
if best_face:
|
|
iFaceNormal = best_face.no
|
|
|
|
|
|
if DISPLACE_NORMAL_MODE == 2: # MEDIAN NORMAL
|
|
if (STATIC_NORMAL and medainNormal == None) or not STATIC_NORMAL or str(medainNormal.x) == 'nan':
|
|
medainNormal = Vector(0,0,0)
|
|
if brush_verts:
|
|
for v, l in brush_verts:
|
|
medainNormal += v.no*(BRUSH_RADIUS-l)
|
|
medainNormal.normalize()
|
|
|
|
|
|
|
|
# ================================================================#
|
|
# == Tool code, loop on the verts and operate on them ============#
|
|
# ================================================================#
|
|
if BRUSH_MODE == 1: # NORMAL PAINT
|
|
for v,l in brush_verts:
|
|
if XPLANE_CLIP:
|
|
origx = False
|
|
if abs(v.co.x) < 0.001: origx = True
|
|
|
|
|
|
v.sel = 1 # MARK THE VERT AS DIRTY.
|
|
falloff = BRUSH_PRESSURE * ((BRUSH_RADIUS-l) / BRUSH_RADIUS) # falloff between 0 and 1
|
|
if DISPLACE_NORMAL_MODE == 1: # VERTEX NORMAL
|
|
if STATIC_NORMAL:
|
|
try:
|
|
no = vert_orig_normals[v]
|
|
except:
|
|
no = vert_orig_normals[v] = v.no
|
|
v.co += no * falloff
|
|
else:
|
|
v.co += no * falloff
|
|
elif DISPLACE_NORMAL_MODE == 2: # MEDIAN NORMAL # FIXME
|
|
v.co += medainNormal * falloff
|
|
|
|
elif DISPLACE_NORMAL_MODE == 3: # FACE NORMAL
|
|
v.co += iFaceNormal * falloff
|
|
elif DISPLACE_NORMAL_MODE == 4: # VIEW NORMAL
|
|
v.co += Direction * falloff
|
|
# Clamp back to original x if needs be.
|
|
if XPLANE_CLIP and origx:
|
|
v.co.x = 0
|
|
|
|
elif BRUSH_MODE == 2: # SCALE
|
|
for v,l in brush_verts:
|
|
|
|
if XPLANE_CLIP:
|
|
origx = False
|
|
if abs(v.co.x) < 0.001: origx = True
|
|
|
|
v.sel = 1 # MARK THE VERT AS DIRTY.
|
|
falloff = (BRUSH_RADIUS-l) / BRUSH_RADIUS # falloff between 0 and 1
|
|
|
|
vert_scale_vec = v.co - best_isect
|
|
vert_scale_vec.normalize()
|
|
# falloff needs to be scaled for this tool
|
|
falloff = falloff / 10
|
|
v.co += vert_scale_vec * (BRUSH_PRESSURE * falloff)# FLAT BRUSH
|
|
|
|
# Clamp back to original x if needs be.
|
|
if XPLANE_CLIP and origx:
|
|
v.co.x = 0
|
|
|
|
if BRUSH_MODE == 3: # ROTATE.
|
|
|
|
if DISPLACE_NORMAL_MODE == 1: # VERTEX NORMAL
|
|
ROTATE_MATRIX = Mathutils.RotationMatrix(BRUSH_PRESSURE*10, 4, 'r', iFaceNormal) # Cant use vertex normal, use face normal
|
|
elif DISPLACE_NORMAL_MODE == 2: # MEDIAN NORMAL
|
|
ROTATE_MATRIX = Mathutils.RotationMatrix(BRUSH_PRESSURE*10, 4, 'r', medainNormal) # Cant use vertex normal, use face normal
|
|
elif DISPLACE_NORMAL_MODE == 3: # FACE NORMAL
|
|
ROTATE_MATRIX = Mathutils.RotationMatrix(BRUSH_PRESSURE*10, 4, 'r', iFaceNormal) # Cant use vertex normal, use face normal
|
|
elif DISPLACE_NORMAL_MODE == 4: # VIEW NORMAL
|
|
ROTATE_MATRIX = Mathutils.RotationMatrix(BRUSH_PRESSURE*10, 4, 'r', Direction) # Cant use vertex normal, use face normal
|
|
# Brush code
|
|
|
|
for v,l in brush_verts:
|
|
|
|
if XPLANE_CLIP:
|
|
origx = False
|
|
if abs(v.co.x) < 0.001: origx = True
|
|
|
|
# MARK THE VERT AS DIRTY.
|
|
v.sel = 1
|
|
falloff = (BRUSH_RADIUS-l) / BRUSH_RADIUS # falloff between 0 and 1
|
|
|
|
# Vectors handeled with rotation matrix creation.
|
|
rot_vert_loc = (ROTATE_MATRIX * (v.co-best_isect)) + best_isect
|
|
v.co = (v.co*(1-falloff)) + (rot_vert_loc*(falloff))
|
|
|
|
# Clamp back to original x if needs be.
|
|
if XPLANE_CLIP and origx:
|
|
v.co.x = 0
|
|
|
|
elif BRUSH_MODE == 4: # RELAX
|
|
vert_orig_loc = [Vector(v.co) for v in me.verts ] # save orig vert location.
|
|
#vertOrigNor = [Vector(v.no) for v in me.verts ] # save orig vert location.
|
|
|
|
# Brush code
|
|
for v,l in brush_verts:
|
|
|
|
if XPLANE_CLIP:
|
|
origx = False
|
|
if abs(v.co.x) < 0.001: origx = True
|
|
|
|
v.sel = 1 # Mark the vert as dirty.
|
|
falloff = (BRUSH_RADIUS-l) / BRUSH_RADIUS # falloff between 0 and 1
|
|
connected_verts = verts_connected_by_edge[v.index]
|
|
relax_point = reduce(lambda a,b: a + vert_orig_loc[b.index], connected_verts, Mathutils.Vector(0,0,0)) * (1.0/len(connected_verts))
|
|
falloff = falloff * BRUSH_PRESSURE
|
|
# Old relax.
|
|
#v.co = (v.co*(1-falloff)) + (relax_point*(falloff))
|
|
|
|
ll = (v.co-relax_point).length
|
|
newpoint = (v.co*(1-falloff)) + (relax_point*(falloff)) - v.co
|
|
newpoint = newpoint * (1/(1+ll))
|
|
v.co = v.co + newpoint
|
|
|
|
'''
|
|
# New relax
|
|
relax_normal = vertOrigNor[v.index]
|
|
v1,v2,v3,v4 = v.co, v.co+relax_normal, relax_point-(relax_normal*10), relax_point+(relax_normal*10)
|
|
print v1,v2,v3,v4
|
|
try:
|
|
a,b = LineIntersect(v1,v2,v3,v4) # Scale the normal to make a line. we know we will intersect with.
|
|
v.co = (v.co*(1-falloff)) + (a*(falloff))
|
|
except:
|
|
pass
|
|
'''
|
|
|
|
# Clamp back to original x if needs be.
|
|
if XPLANE_CLIP and origx:
|
|
v.co.x = 0
|
|
|
|
elif BRUSH_MODE == 5: # GOO
|
|
#print last_best_isect, best_isect, 'AA'
|
|
if not last_best_isect:
|
|
last_best_isect = best_isect
|
|
|
|
# Set up a triangle orthographic to the view plane
|
|
gooPlane = [best_isect, CrossVecs(best_isect, Direction), None]
|
|
|
|
|
|
if DISPLACE_NORMAL_MODE == 4: # View Normal
|
|
tempRotMatrix = Mathutils.RotationMatrix(90, 3, 'r', Direction)
|
|
else:
|
|
tempRotMatrix = Mathutils.RotationMatrix(90, 3, 'r', CrossVecs(best_face.no, Direction))
|
|
|
|
gooPlane[2] = best_isect + (tempRotMatrix * gooPlane[1])
|
|
gooPlane[1] = gooPlane[1] + best_isect
|
|
|
|
continue # we need another point of reference.
|
|
|
|
elif last_best_isect == best_isect:
|
|
# Mouse has not moved, no point in trying to goo.
|
|
continue
|
|
else:
|
|
if goo_dir_vec:
|
|
last_goo_dir_vec = goo_dir_vec
|
|
# The direction the mouse moved in 3d space. use for gooing
|
|
|
|
# Modify best_isect so its not moving allong the view z axis.
|
|
# Assume Origin hasnt changed since the view wont change while the mouse is drawing. ATM.
|
|
best_isect = Intersect( gooPlane[0], gooPlane[1], gooPlane[2], Direction, Origin, 0) # Non clipped
|
|
goo_dir_vec = (best_isect - last_best_isect) * 2
|
|
|
|
|
|
# make a goo rotation matrix so the head of the goo rotates with the mouse.
|
|
"""
|
|
if last_goo_dir_vec and goo_dir_vec != last_goo_dir_vec:
|
|
'''
|
|
vmi = Matrix(Window.GetViewMatrix()); vmi.invert() # the inverse viewing matrix
|
|
a = last_goo_dir_vec * vmi
|
|
b = goo_dir_vec * vmi
|
|
c = Angle2D(a.x, a.y, b.x, b.y)
|
|
gooRotMatrix = Mathutils.RotationMatrix((c * goo_dir_vec.length)*-20, 3, 'r', Direction)
|
|
'''
|
|
pass
|
|
else:
|
|
gooRotMatrix = None
|
|
"""
|
|
|
|
if goo_dir_vec.x == 0 and goo_dir_vec.y == 0 and goo_dir_vec.z == 0:
|
|
continue
|
|
|
|
# Brush code
|
|
for v,l in brush_verts:
|
|
|
|
if XPLANE_CLIP:
|
|
origx = False
|
|
if abs(v.co.x) < 0.001: origx = True
|
|
|
|
# MARK THE VERT AS DIRTY.
|
|
v.sel = 1
|
|
|
|
''' # ICICLES!!!
|
|
a = AngleBetweenVecs(goo_dir_vec, v.no)
|
|
if a > 66:
|
|
continue
|
|
|
|
l = l * ((1+a)/67.0)
|
|
l = max(0.00000001, l)
|
|
'''
|
|
|
|
falloff = (BRUSH_RADIUS-l) / BRUSH_RADIUS # falloff between 0 and 1
|
|
goo_loc = (v.co*(1-falloff)) + ((v.co+goo_dir_vec) *falloff)
|
|
|
|
v.co = (goo_loc*BRUSH_PRESSURE) + (v.co*(1-BRUSH_PRESSURE))
|
|
|
|
'''
|
|
if gooRotMatrix:
|
|
rotatedVertLocation = (gooRotMatrix * (v.co-best_isect)) + best_isect
|
|
v.co = (v.co*(1-falloff)) + (rotatedVertLocation*(falloff))
|
|
# USe for goo only.
|
|
'''
|
|
|
|
# Clamp back to original x if needs be.
|
|
if XPLANE_CLIP and origx:
|
|
v.co.x = 0
|
|
|
|
|
|
# Remember for the next sample
|
|
last_best_isect = best_isect
|
|
last_goo_dir_vec = goo_dir_vec
|
|
|
|
# Post processing after the verts have moved
|
|
# Subdivide any large edges, all but relax.
|
|
|
|
MAX_SUBDIV = 10 # Maximum number of subdivisions per redraw. makes things useable.
|
|
SUBDIV_COUNT = 0
|
|
# Cant use adaptive geometry for relax because it keeps connectivity data.
|
|
if ADAPTIVE_GEOMETRY and (BRUSH_MODE == 1 or BRUSH_MODE == 2 or BRUSH_MODE == 3 or BRUSH_MODE == 5):
|
|
Mesh.Mode(Mesh.SelectModes['EDGE'])
|
|
orig_len_edges = 0
|
|
#print 'ADAPTIVE_GEOMETRY'
|
|
while len(me.edges) != orig_len_edges and SUBDIV_COUNT < MAX_SUBDIV:
|
|
#print 'orig_len_edges', len(me.edges)
|
|
#me = ob.getData(mesh=1)
|
|
orig_len_edges = len(me.edges)
|
|
EDGE_COUNT = 0
|
|
for ed in me.edges:
|
|
if ed.v1.sel or ed.v2.sel:
|
|
l = (ed.v1.co - ed.v2.co).length
|
|
if l > max(RESOLUTION_MIN*1.5, BRUSH_RADIUS):
|
|
#if l > BRUSH_RADIUS:
|
|
#print 'adding edge'
|
|
ed.flag |= SEL_FLAG
|
|
#ed.flag = 35
|
|
SUBDIV_COUNT += 1
|
|
EDGE_COUNT +=1
|
|
"""
|
|
elif l < RESOLUTION_MIN:
|
|
'''
|
|
print 'removing edge'
|
|
v1 =e.v1
|
|
v2 =e.v2
|
|
v1.co = v2.co = (v1.co + v2.co) * 0.5
|
|
v1.sel = v2.sel = 1
|
|
me.remDoubles(0.001)
|
|
me = ob.getData(mesh=1)
|
|
break
|
|
'''
|
|
# Remove edge in python
|
|
print 'removing edge'
|
|
v1 =ed.v1
|
|
v2 =ed.v2
|
|
v1.co = v2.co = (v1.co + v2.co) * 0.5
|
|
|
|
removeDouble(v1, v2, me)
|
|
me = ob.getData(mesh=1)
|
|
break
|
|
"""
|
|
|
|
if EDGE_COUNT:
|
|
me.subdivide(1)
|
|
me = ob.getData(mesh=1)
|
|
filter(lambda ed: setattr(ed, 'flag', 32), me.edges)
|
|
|
|
# Deselect all, we know theres only 2 selected
|
|
'''
|
|
for ee in me.edges:
|
|
if ee.flag & SEL_FLAG:
|
|
#ee.flag &= ~SEL_FLAG
|
|
ee.flag = 32
|
|
elif l < RESOLUTION_MIN:
|
|
print 'removing edge'
|
|
e.v1.co = e.v2.co = (e.v1.co + e.v2.co) * 0.5
|
|
me.remDoubles(0.001)
|
|
break
|
|
'''
|
|
# Done subdividing
|
|
# Now remove doubles
|
|
#print Mesh.SelectModes['VERT']
|
|
#Mesh.Mode(Mesh.SelectModes['VERTEX'])
|
|
|
|
filter(lambda v: setattr(v, 'sel', 1), me.verts)
|
|
filter(lambda v: setattr(v[0], 'sel', 0), brush_verts)
|
|
|
|
# Cycling editmode is too slow.
|
|
|
|
remdoubles = False
|
|
for ed in me.edges:
|
|
|
|
if (not ed.v1.sel) and (not ed.v1.sel):
|
|
if XPLANE_CLIP:
|
|
# If 1 vert is on the edge and abother is off dont collapse edge.
|
|
if (abs(ed.v1.co.x) < 0.001) !=\
|
|
(abs(ed.v2.co.x) < 0.001):
|
|
continue
|
|
l = (ed.v1.co - ed.v2.co).length
|
|
if l < RESOLUTION_MIN:
|
|
ed.v1.sel = ed.v2.sel = 1
|
|
newco = (ed.v1.co + ed.v2.co)*0.5
|
|
#ed.v1.co.x = ed.v2.co.x = newco.x
|
|
#ed.v1.co.y = ed.v2.co.y = newco.y
|
|
#ed.v1.co.z = ed.v2.co.z = newco.z
|
|
ed.v1.co[:]= ed.v2.co[:]= newco
|
|
remdoubles = True
|
|
|
|
#if remdoubles:
|
|
|
|
|
|
filter(lambda v: setattr(v, 'sel', 0), me.verts)
|
|
#Mesh.Mode(Mesh.SelectModes['EDGE'])
|
|
# WHILE OVER
|
|
# Clean up selection.
|
|
#for v in me.verts:
|
|
# v.sel = 0
|
|
'''
|
|
for ee in me.edges:
|
|
if ee.flag & SEL_FLAG:
|
|
ee.flag &= ~SEL_FLAG
|
|
#ee.flag = 32
|
|
'''
|
|
filter(lambda ed: setattr(ed, 'flag', 32), me.edges)
|
|
|
|
|
|
if XPLANE_CLIP:
|
|
filter(lambda v: setattr(v.co, 'x', max(0, v.co.x)), me.verts)
|
|
|
|
|
|
me.update()
|
|
#Window.SetCursorPos(best_isect.x, best_isect.y, best_isect.z)
|
|
Window.Redraw(Window.Types.VIEW3D)
|
|
#Window.DrawProgressBar(1.0, '')
|
|
if STATIC_MESH:
|
|
#try:
|
|
mesh_static.verts = None
|
|
print len(mesh_static.verts)
|
|
mesh_static.update()
|
|
#except:
|
|
# pass
|
|
if FIX_TOPOLOGY:
|
|
fix_topolagy(me)
|
|
|
|
# Remove short edges of we have edaptive geometry enabled.
|
|
if ADAPTIVE_GEOMETRY:
|
|
Mesh.Mode(Mesh.SelectModes['VERTEX'])
|
|
filter(lambda v: setattr(v, 'sel', 1), me.verts)
|
|
me.remDoubles(0.001)
|
|
print 'removing doubles'
|
|
me = ob.getData(mesh=1) # Get new vert data
|
|
Blender.event = Draw.LEFTMOUSE
|
|
|
|
Mesh.Mode(Mesh.SelectModes['EDGE'])
|
|
|
|
if i:
|
|
Window.EditMode(1)
|
|
if not is_editmode: # User was in edit mode, so stay there.
|
|
Window.EditMode(0)
|
|
print '100 draws in %.6f' % (((Blender.sys.time()-time) / float(i))*100)
|
|
Blender.event = None
|
|
|
|
if __name__ == '__main__':
|
|
event_main() |