[Bf-scripts-dev] Sculpt mesh
Tom Musgrove
tommusgrove__ at hotmail.com
Sun Oct 17 13:12:54 CEST 2004
Okay here is an update to the sculpt mesh tool, it now has proper
registration information.
Also this version does 'binning' - that is, the view area is subdivided into
a grid, and vertices within that grid are assigned that grid.
The four corners of the alphamap are located to the grid and only the faces
and vertices within the grids that are shared by the mouse are examined,
whereas in the previous version all faces and vertices were examined.
For some reason, the window events are passed properly when using it is
activated via the scripts window, but not when ran via Alt-P
However, I'm getting a segfault immediately after I rotate it, so I might
disable rotation for now unless it can be tracked down.
Tom M.
LetterRip
_________________________________________________________________
Express yourself instantly with MSN Messenger! Download today - it's FREE!
hthttp://messenger.msn.click-url.com/go/onm00200471ave/direct/01/
-------------- next part --------------
#!BPY
"""
Name: 'Sculpt Mesh...'
Blender: 235
Group: 'Mesh'
Tip: 'Sculpts mesh pushing, pulling and twisting'
"""
# $Id:sculpt_mesh.py,v .2 2004/10/17 02:16:31 broken Exp $
#
#===============================================#
# Sculpt Mesh v 0.2 by Tom Musgrove (LetterRip) #
# and Michael Scardt #
# if you have any questions about this script #
# email LetterRip at gmail dot com #
# #
#===============================================#
# --------------------------------------------------------------------------
# Sculpt Mesh v 0.2 by Tom Musgrove (LetterRip) and Michael Scardt
# --------------------------------------------------------------------------
# ***** 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 Blender
from Blender import Object, NMesh, Window, Draw, Mathutils, Types
from Blender.Mathutils import *
from math import sin, cos, sqrt, pi, hypot
# Import Psyco if available, psyco can often give substantial speed ups
try:
import psyco
# psyco.log()
# psyco.profile()
psyco.full()
except ImportError:
pass
displaceMode = 1 # 0 is off, 1 is displaceOut, -1 is displaceIn, 2 is Smooth
# smooth isn't implemented yet
displacementHeight = 1.0 #
radiusMinimum = 5 # the smallest size of the selection radius in pixels
radiusMaximum = 20 #this gives us a grid size of 100 x 100 which isn't that
big of a savings
#the smaller the grid size the greater the savings
alpha_size_x = alpha_size_y = 2*radiusMaximum
# no user friendly gui yet
# to change the formula used, change fallofftype to
# one of None, Linear, Sin, Cos, SquareRoot, Squared, Cubic, and Quadratic
# then change the selectionRadius
# the xsize and ysize of the image are the first two numbers of the MaskID
# except in the case of the radial functions, in which case it is the radius
# also may change it to inner and outer radius or other such stuff
# the string is the id of the mask ID for formula based it is falloffType +
maxRadius
falloffType = "Linear"
selection_size_x = selection_size_y = selectionRadius = 20
maskName = falloffType+repr(selectionRadius)
MaskID = (selectionRadius,selectionRadius,maskName)
win_min_x = win_min_y = win_max_x = win_max_y = win_mid_x = win_mid_y =
win_size_x = win_size_y = 0
#the above are the corners of the 3d view window, and the scaling factors
mouseX = mouseY = 0
obj_mat_times_persp_mat = Matrix()
myMesh = NMesh.New("temp")
selectedVerts = []
dictOfAlphaMasks = {}
myObject = Blender.Object.New('Mesh')
lastViewVector = [-100,100,-100] #right now these doesn't do much
currentViewVector = [-100,-100,-100] #but, if we allow screen rotation then
they will trigger an update of the matrices
obj_mat = Matrix() #contains the matrix of the object (local? or global? I
think global.)
perspMatrix = Matrix() #contains the perspective Matrix, and allows the
screen coordinates to be projected to the screen
dictFaces = {}
faceNormalWorldVecDict = {}
facedotVecDict = {}
vectorDirtyDict = {}
cacheVecCoMultDict = {}
vectorDirty = False
# gets the mesh data from the selected object
# finds the selected verts
# and then selects verts based on the alpha mask
def initialize():
global myObject, myMesh, currentViewVector, lastViewVector
myObject = Object.GetSelected()[0]
if myObject.getType() == 'Mesh':
myMesh = myObject.getData()
name = myObject.name
else:
print "we either did not have a mesh or did not have an object selected"
mouseX, mouseY = Window.GetMouseCoords()
updateViewCoords(mouseX, mouseY)
mouseInView3DWindow(mouseX, mouseY)
currentViewVector = Window.GetViewVector()
lastViewVector = currentViewVector
initializeFaceLists()
updateRadius(MaskID[0], 0, falloffType) #this initializes the starting
alphamask
#we could actually create two lists from each, one that contains the faces
with normals towards the
#camera, and the other without, and thus traverse our faces a bit faster...
#also we could use smaller grid subdivisions, which means fewer faces and
verts per bin
#which means likely faster
def initializeFaceLists():
selection = {}
global myMesh, dictFaces, faceNormalWorldVecDict, facedotVecDict
global vectorDirtyDict, cacheVecCoMultDict
dictFaces = {}
faceNormalWorldVecDict = {}
facedotVecDict = {}
vectorDirtyDict = {}
cacheVecCoMultDict = {}
global alpha_size_x
global alpha_size_y
global VectorDirty
facedotVec = 0.0
VectorDirty = False
viewVector = Vector(Window.GetViewVector())
objectWorldMatrix = myObject.getMatrix('worldspace')
perspMatrix = Blender.Window.GetPerspMatrix()
obj_mat_times_persp_mat = objectWorldMatrix*perspMatrix
for face in myMesh.faces:
gfn = VecMultMat(Vector(list(face.normal[:3])+[1.0]), objectWorldMatrix)
gfn = Vector(list(gfn[:3]))
faceNormalWorldVecDict[face] = gfn
facedotVecDict[face] = DotVecs(gfn, viewVector)
for vertex in face.v:
if vertex not in selection:
#calculate the vertex screen coordinates...
vectorDirtyDict[vertex.index] = vertex.co
tempVec = vertex.co
hvs = VecMultMat(Vector(list(tempVec[:3])+[1.0]),
obj_mat_times_persp_mat)
hvs[0] /= hvs[3]
hvs[1] /= hvs[3]
vs = [int(win_mid_x + (hvs[0] * win_size_x)),
int(win_mid_y + (hvs[1] * win_size_y))]
cacheVecCoMultDict[vertex.index] = vs
else:
vs = cacheVecCoMultDict[vertex.index]
##### now ...
vert_grid_x = vs[0]/alpha_size_x
vert_grid_y = vs[1]/alpha_size_y
tempList = []
try:
tempList = dictFaces[(vert_grid_x, vert_grid_y)]
tempList.append(face)
except KeyError:
tempList.append(face)
dictFaces[(vert_grid_x, vert_grid_y)] = tempList
selection[vertex] = True
####and now we remove the duplicate faces
for key in dictFaces.keys():
listAppended = dictFaces[key]
nd={}
for f in listAppended:
nd[f]=None
dictFaces[key] = nd.keys()
# takes a vert and based on its screen location relative to the mouse
# is displaced according to an alphaMask
# radius and falloff are done via computing the mask once
# the alphaMaskDict is specified by a MaskID
# mask ID is a tuple
# with four values,
# Xoffset, Yoffset, "maskName"
# for radius functions, maskName is "FallOffTypeRadius"
# ie for a linear of radius 8, it would be Linear8
# and the tuple would be (8,8,"Linear8")
# for imagebased, it is usually the image name
def DisplaceSelection(MaskID, mouseX, mouseY, displaceMode=0,
displacementHeight= 0.0):
global myMesh, dictOfAlphaMasks, alpha_size_x, alpha_size_y, dictFaces,
faceNormalWorldVecDict
global facedotVecDict, cacheVecCoMultDict, vectorDirtyDict
selection = {}
if MaskID not in dictOfAlphaMasks:
# should handle image masks as well, but for now, we will handle only the
radius masks
updateRadius(MaskID[0], 0, falloffType)
alphaMaskDict = dictOfAlphaMasks[MaskID]
maskOffset = []
maskOffset.append(MaskID[0])
maskOffset.append(MaskID[1])
displaceMH = displacementHeight*displaceMode
mouse_and_maskX = maskOffset[0] - mouseX
mouse_and_maskY = maskOffset[1] - mouseY
viewVector = Vector(Window.GetViewVector())
objectWorldMatrix = myObject.getMatrix('worldspace')
perspMatrix = Blender.Window.GetPerspMatrix()
obj_mat_times_persp_mat = objectWorldMatrix*perspMatrix
emptyVector = Vector()
#### here we find which grids the alphamask is in, and return a list of
faces
try:
list1 = dictFaces[((mouseX + maskOffset[0])/alpha_size_x, (mouseY -
maskOffset[1])/alpha_size_y)]
except KeyError:
list1 = []
try:
list2 = dictFaces[((mouseX + maskOffset[0])/alpha_size_x, (mouseY +
maskOffset[1])/alpha_size_y)]
except KeyError:
list2 = []
try:
list3 = dictFaces[((mouseX - maskOffset[0])/alpha_size_x, (mouseY -
maskOffset[1])/alpha_size_y)]
except KeyError:
list3 = []
try:
list4 = dictFaces[((mouseX - maskOffset[0])/alpha_size_x, (mouseY +
maskOffset[1])/alpha_size_y)]
except KeyError:
list4 = []
listAppended = list1+list2+list3+list4
####and now we remove the duplicate faces
nd={}
for f in listAppended:
nd[f]=None
listAppended = nd.keys()
for face in myMesh.faces:
if (face in listAppended):
if faceNormalWorldVecDict[face] == emptyVector:
gfn = VecMultMat(Vector(list(face.normal[:3])+[1.0]), objectWorldMatrix)
gfn = Vector(list(gfn[:3]))
faceNormalWorldVecDict[face] = gfn
facedotVecDict[face] = DotVecs(gfn, viewVector)
elif vectorDirty:
gfn = faceNormalWorldVecDict[face]
facedotVecDict[face] = DotVecs(gfn, viewVector)
facedotVec = facedotVecDict[face]
if facedotVec > 0: # simple backface-culling
faceDirtyCount = 0
for vertex in face.v:
if (vertex.index not in selection):
# we need to calculate the distance of the vert from the mouse
# thus we need either the screen x and y of the vert
# or the UV x,y of the mouse and of the vert
# the offset is the distance from the mouse location, to the lower
left corner
# of the mask
try:
tempCo = vectorDirtyDict[vertex.index]
except KeyError:
tempCo = None
if tempCo == vertex.co:
vs = cacheVecCoMultDict[vertex.index]
else:
vectorDirtyDict[vertex.index] = vertex.co
tempVec = vertex.co
hvs = VecMultMat(Vector(list(tempVec[:3])+[1.0]),
obj_mat_times_persp_mat)
hvs[0] /= hvs[3]
hvs[1] /= hvs[3]
vs = [int(win_mid_x + (hvs[0] * win_size_x)),
int(win_mid_y + (hvs[1] * win_size_y))]
cacheVecCoMultDict[vertex.index] = vs
deltaX = vs[0] + mouse_and_maskX
deltaY = vs[1] + mouse_and_maskY
try:
thisDisplacement = alphaMaskDict[(deltaX, deltaY)]
vertex.sel = 1
except KeyError:
thisDisplacement = 0.0
vertex.sel = 0
if vertex.sel == 1:
#if we are just updating the selection
if displaceMode == 0:
pass
#if we are displacing the vertex out
#or if we are displacing the vertex in
elif (displaceMode == 1) or (displaceMode == -1):
tempDisplace = thisDisplacement*displaceMH
vertex.co[0] += vertex.no[0]*tempDisplace
vertex.co[1] += vertex.no[1]*tempDisplace
vertex.co[2] += vertex.no[2]*tempDisplace
#since the vertex changed,
#we need to update the faces normal
#information based on the new vertex location
faceDirtyCount += 1
###we need to find the new screen location of the moved verts
### and if they are in a new grid locations we need to add the face to the
new grids face list
# we should probably pop the face off of its original face lists if we
expect verts to change
# quadrants frequently otherwise our facelists will soon have faces that
shouldn't be in it...
# of course we may rebuild our face list often enough that this might not
be a significant issue
# perhaps we should rebuild a single face list based on its length
#we might also consider subdividing a grid if it has more than a certain
number of faces
#this is to let the loop know that we've already handled this vertex
#so it doesn't do calculations on it twice
selection[vertex.index]=True
#right now we don'l have smooth implemented
if(faceDirtyCount > 0):
faceNormalWorldVecDict[face] = emptyVector
#displaces verts towards the vert average normal
def SmoothSelection():
#we need to first calculate the average normal for the selected verts
#probably we should exlude outliers to give better results
#then find the difference between average normal and the current verts
normal
print "we smoothed the selection"
pass
# takes an image and its size (or just an image and gets the size from the
image?
# use code ideas from this link to implement
# http://www.elysiun.com/forum/viewtopic.php?t=16326&highlight=getpixel
# instead of doing positive and negative numbers
# i define all locations from the lower left corner
def alphaMaskDictFromAlphaMap(xsize, ysize, alphamap):
global dictOfAlphaMasks
alphaMaskDict = {}
for x in range(0, xsize):
for y in range (0, ysize):
alphacoords = (x,y)
thisDisplacement = alphamap.alphacoords
alphaMaskDict[alphacoords] = thisDisplacement
MaskID = (int(xsize/2),int(ysize/2), alphamap.name)
dictOfAlphaMasks[MaskID] = alphaMaskDict
#
#
def updateRadius(selectionRadius, deltaRadius, falloffType):
global MaskID, dictOfAlphaMasks
print "update radius"
selectionRadius += deltaRadius
if selectionRadius < radiusMinimum:
selectionRadius = radiusMinimum
if selectionRadius > radiusMaximum:
selctionRadius = radiusMaximum
maskName = falloffType+repr(selectionRadius)
MaskID = (selectionRadius, selectionRadius, maskName)
if MaskID not in dictOfAlphaMasks:
dictOfAlphaMasks[MaskID] = alphaMaskDictFromRadius(selectionRadius,
falloffType)
selection_size_x = selection_size_y = selectionRadius
return selectionRadius
def updateDisplacement(displacementHeight, deltaHeight):
displacementHeight = displacementHeight + deltaHeight
return displacementHeight
#
# not needed but would be nice
def drawSelectionCircle(selectionRadius):
#print "Drawing the selection circle"
pass
# creates an alphamask from the radius supplied
# this gives two benefits - the first is that we can use any alphamask
# instead of using just radius based functions with falloff
# the second is that this gives us a lookup table and we don't have to
# calculate the radius function a bazillion times
def alphaMaskDictFromRadius(radius = 8, falloffType = "None"):
#note that instead of negative numbers, I'm using an offset of
#of the radius, and use all positive numbers
thisRadius2 = 0
radius2 = radius*radius
alphaMaskDict = {}
for x in range(0, radius):
for y in range(0, radius):
thisRadius2 = x^2+y^2
if thisRadius2 <= radius2:
thisRadius = sqrt(thisRadius2)
thisDisplacement = radiusFunction(thisRadius, radius, falloffType)
alphaMaskDict[( x+radius,-y+radius)]=thisDisplacement
alphaMaskDict[(-x+radius, y+radius)]=thisDisplacement
alphaMaskDict[(-x+radius,-y+radius)]=thisDisplacement
alphaMaskDict[( x+radius, y+radius)]=thisDisplacement
return alphaMaskDict
# falloffTypes are None, Linear, Sin, Cos, SquareRoot, Squared, Cubic, and
Quadratic
# these are adapted from 'Taper and Twist'
# by flippyneck
def radiusFunction(thisRadius, maxRadius, falloffType="None"):
if falloffType == "None":
return 1.0
if falloffType == "Cubic":
return interpolateCubic(0,maxRadius, thisRadius)
elif falloffType == "Quadratic":
return interpolateQuadratic(0,maxRadius, thisRadius)
elif falloffType == "Linear":
return interpolateLinear(0, maxRadius, thisRadius)
elif falloffType == "Sin":
return interpolateSin(0, maxRadius, thisRadius)
elif falloffType == "Cos":
return interpolateCos(0,maxRadius, thisRadius)
elif falloffType == "SquareRoot":
return interpolateRoot(0,maxRadius, thisRadius)
elif falloffType == "Squared":
return interpolateLinear(0,maxRadius, thisRadius)
def interpolateLinear(min, max, myVal):
''' returns 0.0 <= myVal <=1.0 '''
return myVal/float((max-min))
def interpolateSin(min, max, myVal):
n=interpolateLinear(min, max, myVal)
return sin(n)
def interpolateCos(min, max, myVal):
n=interpolateLinear(min, max, myVal)
return cos(n)
def interpolateSquared(min, max, myVal):
n=interpolateLinear(min, max, myVal)
return n**2
def interpolateRoot(min, max, myVal):
n=interpolateLinear(min, max, myVal)
return sqrt(n)
def interpolateCubic(min, max, myVal):
n=interpolateLinear(min, max, myVal)
return n**3
def interpolateQuadratic(min, max, myVal):
n=interpolateLinear(min, max, myVal)
return n**4
#
********************************************************************************
# init screen coordinates
# these should be updated if the screen is resized, etc.
def updateViewCoords(screen_x, screen_y):
global win3d, win_mid_x, win_mid_y, win_max_x, win_max_y, win_min_x,
win_min_y
global win_size_x, win_size_y, acceptable_min_x, acceptable_min_y,
acceptable_max_x, acceptable_max_y
global selection_size_x, selection_size_y
for win3d in Blender.Window.GetScreenInfo(Blender.Window.Types.VIEW3D):
win_min_x, win_min_y, win_max_x, win_max_y = win3d.get('vertices')
win_mid_x = (win_max_x + win_min_x) * 0.5
win_mid_y = (win_max_y + win_min_y) * 0.5
win_size_x = (win_max_x - win_min_x) * 0.5
win_size_y = (win_max_y - win_min_y) * 0.5
acceptable_min_x = screen_x - selection_size_x
acceptable_min_y = screen_y - selection_size_y
acceptable_max_x = screen_x + selection_size_x
acceptable_max_y = screen_y + selection_size_y
if (win_max_x > screen_x > win_min_x) and (win_max_y > screen_y >
win_min_y):
return True
else:
return False
def mouseInView3DWindow(mouseX, mouseY):
# we calculate the screens midpoints and the scaling factor for the screen
# and then check if the mouse is in the view3d area
# we should really only do the calculations the first time
# thus it should prbably
global win3d, win_mid_x, win_mid_y, win_max_x, win_max_y, win_min_x,
win_min_y
global win_size_x, win_size_y, acceptable_min_x, acceptable_min_y,
acceptable_max_x, acceptable_max_y
global selection_size_x, selection_size_y
for win3d in Blender.Window.GetScreenInfo(Blender.Window.Types.VIEW3D):
win_min_x, win_min_y, win_max_x, win_max_y = win3d.get('vertices')
if (win_max_x > mouseX > win_min_x) and (win_max_y > mouseY > win_min_y):
return True
else:
return False
# Here is the main loop
mouseStartX, mouseStartY = Window.GetMouseCoords()
mouseX = mouseY = 0
radiusIncrement = 5
updateFrequency = 0 # number of pixels to move before updating the vert
selection
selectedVerts = []
updateBounds = True
initialize()
done = 0
while not done:
evt, val = Window.QRead()
if evt in [4]: #Draw.RIGHTMOUSE is 3 which is wrong...
# first we need to check if the mouse is inside the view3d boundbox
currentViewVector = Window.GetViewVector()
if currentViewVector != lastViewVector:
lastViewVector = currentViewVector
#vectorDirty = True
#instead we will initialize the face lists...
#this should probably be done elsewhere so that there isn't a huge lag on
the firt RMB
#press...
initializeFaceLists()
while Window.GetMouseButtons() == 4:
if Window.GetKeyQualifiers() == 0:
mouseX, mouseY = Window.GetMouseCoords()
if mouseInView3DWindow(mouseX, mouseY):
# determine if the mouse has moved far enough for an update
mouseDistX = mouseStartX - mouseX
mouseDistY = mouseStartY - mouseY
mouseDistanceMoved = hypot(mouseDistX, mouseDistY)
if mouseDistanceMoved >= updateFrequency:
mouseStartX, mouseStartY = mouseX, mouseY
old_mode = Window.EditMode()
Window.EditMode(0)
DisplaceSelection(MaskID, mouseX, mouseY, displaceMode,
displacementHeight)
myMesh.update()
Window.EditMode(old_mode)
Blender.Window.Redraw(Window.Types.VIEW3D)
elif Window.GetKeyQualifiers() != 0:
if displaceMode == +1: displacement_string = "out"
elif displaceMode == -1: displacement_string = "in"
menu_result = Draw.PupMenu("Interactive Paint%t|%l|Selection Size:
"+str(selectionRadius)+"%x1|Displacement:
"+displacement_string+"%x2|%l|Quit%x3")
if (menu_result == 1):
selection_size = Draw.PupIntInput("Selection Size:", selectionRadius,
1, 100)
deltaRadius = selection_size - selectionRadius
selectionRadius = updateRadius(selectionRadius, deltaRadius,
falloffType)
if (menu_result == 2):
displaceMode *= -1
if (menu_result == 3):
done = True
Draw.Redraw (Window.QTest())
#drawSelectionCircle(selectionRadius)
# elif evt in [Draw.SKEY]:
# if val:
# displaceMode = 2 #smooth mode
# else:
# displaceMode = 0
elif not val or evt in [Draw.MOUSEX, Draw.MOUSEY]: continue #helps speed
elif evt in [Draw.ESCKEY, Draw.QKEY]: done = True
elif evt in [Draw.LEFTARROWKEY] and val: #shrink radius
selectionRadius = updateRadius(selectionRadius, -radiusIncrement,
falloffType)
elif evt in [Draw.RIGHTARROWKEY] and val: #grow radius
selectionRadius = updateRadius(selectionRadius, radiusIncrement,
falloffType)
elif evt in [Draw.UPARROWKEY] and val: #grow displacementHeight
displacementHeight = updateDisplacement(displacementHeight, 0.1)
elif evt in [Draw.DOWNARROWKEY] and val: #shrink displacementHeight
displacementHeight = updateDisplacement(displacementHeight, -0.1)
else: #otherwise pass the event back and let Blender handle it
id = Window.GetScreenInfo(Window.Types.VIEW3D)[0].get('id')
Window.QAdd(id, evt, val)
Window.QHandle(id)
Blender.Redraw(-1) #don't forget to redraw
pass
More information about the Bf-scripts-dev
mailing list