[Bf-scripts-dev] sculpt mesh
Tom Musgrove
tommusgrove__ at hotmail.com
Sun Oct 17 23:06:26 CEST 2004
The segfault is caused by some sort of interaction with psyco, commenting
out psyco eliminates the segfault...
So, here is the updated script - now you can rotate the mesh and other
common operations that don't require the RMB.
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 Schardt #
# 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
# commented out because psyco causes a segfault if you rotate the mesh for
# some reason...
# 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