[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