Dev:Source/Modeling/Sculpt Mesh

提供: wiki
< Dev:Source‎ | Modeling
2018年6月29日 (金) 02:45時点におけるYamyam (トーク | 投稿記録)による版 (1版 をインポートしました)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
移動先: 案内検索

This is the latest version of Sculpt Mesh - unzip it and place it in your scripts folder, it now has Pen Table Support (using Michael Schardts plugin, Windows only...), which can be downloaded here http://members.fortunecity.de/pytablet , MirrorMode and it can maintain the subsurf level (although this is turned off by default).

MirrorMode is turned off if the mesh is too complex to give accurate mirroring. Also I've just added an 'undoMode' which is toggled with the UKey. It reverts the vert back to its position at the start of the script.

Use this version with 2.37, 2.36 and 2.35

or this version with current CVS


Install Procedure

1) Get release 2.37a (or 2.40alpha1 or CVS)

it can be downloaded from here

http://www.blender3d.org/

2) Download the script unzip it and then place it in your .scripts folder

the script can be downloaded from here

media:Sculpt_mesh_tablet23.zip

(media:Sculpt_mesh_tablet25.zip for CVS)

3) start up blender

4) select a mesh object and and make sure you are in object mode, also make

sure you only have ONE view3d window open (An error message will be given if you have more than one open)

5) goto a scripts window (changing the buttons window would be a good

choice) and then goto Scripts > Mesh > sculpt_mesh_tablet23...

6) LMB on the object and start sculpting

7) Use LMB + CTRLKEY to change the properties (selection radius, and

displacement height) these are also available via hotkeys (see below)

Also you can change whether you are in mirror mode via the properties popup menu

8) FKEY to flip/toggle displacement direction (OUT/IN)

9) UKEY to toggle in and out of undoMode

10) MKEY to toggle in and out of mirrorMode

11) Up and Down Arrowkeys to change the displacement height

12) Left and Right Arrowkeys to change the selection radius

13) ESCKEY to exit


Notes

The material will change to the default while you are sculpting.

Also 'big' meshes can go really slow. Mirror mode only works on extremely symetric meshes basically a cube subdivided to 1500 faces gives good results, but more than that it can give poor results.

Sculpt Mesh utilizes bining so that far fewer faces are examined each time through the loop.

This newest version also only updates the mesh part that is being worked on, which has given another substantial speed up (I can comfortably work on a 120,000 face mesh that displays about 1/4 of the object being sculpted). I'm using projection to local space right now, which is a bit faster, however I'm not sure how to project the alphamaps so instead it does it via a formula.

Demos & Movies

See these demos for an idea of what can be done - note that the frame rate is 3 frames per second so it is much faster than it looks. Also with the binning method, you can zoom in on the mesh and it will speed up the script.


To-Do

Here is the todo list with brief descriptions of how to code them.

  1. Mirroring - I currently have mirroring enabled for the x axis only. Right now I base it on the vertex position relative to the objects center. Thus it is based on the expectation that the mesh is completely symmetrical. Ultimately I would like to base mirroring on converting the mouse coordinates to radial coordinates based on the objects center. This would allow mirroring on any axis (x,y,z or arbitrary) and radial mirroring as well. However, before I can do this, I need to change the way I do binning so that it is based on local space of the object, instead of based on the screen space (the current method).
  2. undo sculpt - this is currently implemented but undos back to the starting position of the vert.
  3. base alphamask on an image http://www.elysiun.com/forum/viewtopic.php?t=16326&highlight=getpixel The alphamask code was based purely on viewspace coordinates. Since I now use localspace for speed reasons, I need to figure out how to project the alphamap into screenspace as well.
  4. move the mesh in a direction other than along the normal - easy, some possibilities are along global coordinates or local coordinates, towards the screen, or constain to a coordinate direction (x,y,z), or constrain to some specified geometry
  5. smooth mode - calculate average face area, and per face area, as well as the selection average area, move the verts towards the center of the large faces
  6. pinch mode - add to the coordinate displacement that is perpendicular to normal at the mouse location
  7. add an inner radius where falloff doesn't happen

TomMusgrove - 12 Nov 2004


Code

#!BPY

""" Registration info for Blender menus:
Name: 'Sculpt Mesh Tablet29' 
Blender: 237
Group: 'Mesh'
Tip: 'Sculpts mesh as if the mesh were clay'
"""

__author__ = ("Tom Musgrove", "Michael Schardt")
__url__ = ("blender", "elysiun", "http://mediawiki.blender.org/index.php/BlenderDev/SculpMesh")
__version__ = "2.38 31/10/05"

__bpydoc__ = """\
This script sculpts a mesh as if it were clay.

sculpt_mesh allows you to push and pull mesh vertices just by dragging the mouse over it.
Allowing easy and fast intuitive modeling.

Supported:<br>
    a basic sculpt tool that allows you to specify a radius of influence and a displacement height. We now can use a pen tablet (MS Windows (TM)) such as Wacom (TM).
    All displacement is along the vertex normals.

Usage:<br>
    To use the script, select a mesh, run the script, then use the LMB to displace mesh,
    and the LMB with the CTRL_KEY for a menu to change properties such as the selection radius
    and displacement height, and mirroring Mode.  For simple meshes Mirroring on the XAxis is
    turned on by default.  For complex meshes it is turned off by default if a vert could not
    be matched with its mirror image.  The FKEY toggles displacement in/out.  The UKEY toggles
    undoMode - which returns the verts that are selected back to their original position.
    To exit the script press the ESCKEY.
    It is recommended that the mesh have subsurface smoothing turned on, that the mesh be in object
    mode, and that the shading method is Solid.
    The tool can be speed up by zooming in on the object.
    
Missing:<br>
    Future versions will allow other directions of displacement to be specified 
    (such as towards the screen, or only along a single local or global axis), 
    mirroring of the displacement (in the x,y,z locally or globally, as well as radial mirroring), 
    allow directional displacement, and allow the usage of alphamaps to specify displacment height and pattern.

Known issues:<br>
    To improve speed the screen is subdivided into grids and then only faces within 
    the same grid as the mouse are evaluated.  If displacement happens on a grid edge 
    then odd results may occur (ie some faces not getting displaced).  Also if a face 
    is displaced beyond a grid boundary it may also give strange results.  Lastly, 
    for faces that are steeply sloping away from the screen view the quandrant evaluation
    can skip over faces that should be included, thus the user should try and limit
    using the script to areas where most of the faces are mostly alighed with the camera. 

Notes:<br>


"""

# $Id: sculpt_mesh_tablet22.py,v 1.9 2005/11/21 22:53:43 fstmm Exp $
#
#=======================================================#
# Sculpt Mesh v 0.32 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.38 by Tom Musgrove (LetterRip) and Michael Schardt
# --------------------------------------------------------------------------
# ***** 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 *
from Blender.Mathutils import *
import math

try:
	import psyco
	psyco.full()
except:
	pass

try:
	import PyTablet
	print (PyTablet.GetVersion()+" found\n")
	if PyTablet.GetVersion() == "PyTablet v1.0b":
		Tablet = PyTablet.New()
		Tablet.SetRange("Pressure", [[0.01, 1.0], [None, None]])
		Tablet.SetFallback("Pressure", [1.0, None])
	else:
		Tablet = None
		print "PyTablet: wrong version number! (need PyTablet v1.0b)"
		print "You can get the latest version of PyTablet at\n http://members.fortunecity.de/pytablet\n"
except:
	Tablet = None
	print "If you would like to use a Wacom or other tablet you can download PyTablet to enable using"
	print "the pen pressure to control the sculpting radius and displacement"
	print "You can get the latest version of PyTablet at\n http://members.fortunecity.de/pytablet\n"
	
# ********************************************************************************
# some math functions:
# --------------------

def cross_v3v3 (vector1, vector2):
	return [vector1[1]*vector2[2] - vector1[2]*vector2[1],
		vector1[2]*vector2[0] - vector1[0]*vector2[2],
		vector1[0]*vector2[1] - vector1[1]*vector2[0]]

def dot_v2v2 (vector1, vector2):
	return vector1[0]*vector2[0] + vector1[1]*vector2[1]

def dot_v3v3 (vector1, vector2):
	return vector1[0]*vector2[0] + vector1[1]*vector2[1] + vector1[2]*vector2[2]

def dot_v4v4 (vector1, vector2):
	return vector1[0]*vector2[0] + vector1[1]*vector2[1] + vector1[2]*vector2[2] + vector1[3]*vector2[3]

def add_v2v2 (vector1, vector2):
	return [vector1[0]+vector2[0], vector1[1]+vector2[1]]

def add_v3v3 (vector1, vector2):
	return [vector1[0]+vector2[0], vector1[1]+vector2[1], vector1[2]+vector2[2]]

def add_v4v4 (vector1, vector2):
	return [vector1[0]+vector2[0], vector1[1]+vector2[1], vector1[2]+vector2[2], vector1[3]+vector2[3]]

def sub_v2v2 (vector1, vector2):
	return [vector1[0]-vector2[0], vector1[1]-vector2[1]]

def sub_v3v3 (vector1, vector2):
	return [vector1[0]-vector2[0], vector1[1]-vector2[1], vector1[2]-vector2[2]]

def sub_v4v4 (vector1, vector2):
	return [vector1[0]-vector2[0], vector1[1]-vector2[1], vector1[2]-vector2[2], vector1[3]-vector2[3]]

def scale_v2s (vector, scalar):
	return [vector[0]*scalar, vector[1]*scalar]

def scale_v3s (vector, scalar):
	return [vector[0]*scalar, vector[1]*scalar, vector[2]*scalar]

def scale_v4s (vector, scalar):
	return [vector[0]*scalar, vector[1]*scalar, vector[2]*scalar, vector[3]*scalar]

def normalize_v2 (vector):
	length = math.sqrt (dot_v2v2 (vector, vector))
	if length != 0.0:	return scale_v2s (vector, 1.0/length)
	return [0.0, 0.0]

def normalize_v3 (vector):
	length = math.sqrt (dot_v3v3 (vector, vector))
	if length != 0.0:	return scale_v3s (vector, 1.0/length)
	return [0.0, 0.0, 0.0]

def normalize_v4 (vector):
	length = math.sqrt (dot_v4v4 (vector, vector))
	if length != 0.0: return scale_v4s (vector, 1.0/length)
	return [0.0, 0.0, 0.0, 0.0]

# ********************************************************************************
#returns the list of faces to check based on the four corners of the brush
def getFaceList(mouseX, mouseY):
	global dictFaces
	global alpha_size_x
	global alpha_size_y
	global maskOffset

	#right now the maskOffset and the alpha_size_x and alpha_size_y
	#are constants, they really should be calculated from the selection_size
	#and current zoom level
	#but they should be 'good enough' as long as we aren't really closely zoomed in
	#or really far zoomed out...
	list1 = []
	list2 = []
	list3 = []
	list4 = []
	listAppended = []
	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()
	return listAppended

def getMouseLocalCoordinates(screen_x, screen_y, useMid = False):
	global myObject
#
# DESCRIPTION:
#
# actually the function name is completely misleading...
#
# the function returns a point and a direction vector in global coordinates.
# The point is the location of the virtual camera of the examined 3dwin (not a blender camera object!)
# The direction vector is the direction of a ray originating from the (vitual)screen at screencoords (screen_x, screen_y)
# and shooting towards this vitual camera position.
#
	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.get('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:
			screen_x = win_mid_x
			screen_y = win_mid_y
		if win_max_x > screen_x > win_min_x and win_max_y > screen_y > win_min_y:
			# if the given screencoords (screen_x, screen_y) are within the 3dwin we fount the right one...
			Window.QHandle(win3d.get('id'))
			# first we handle all pending events for this window (otherwise the matrices might come out wrong)


			# 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...
			vmi  = Matrix(Window.GetViewMatrix())
			vmi.invert()					# the inverse viewing matrix
			pm   = Matrix(Window.GetPerspMatrix()) 			# the prespective matrix
			pmi  = Matrix(Window.GetPerspMatrix())
			pmi.invert()					# the inverted perspective matrix
			
			epsilon = 1e-3 # just a small value to account for floating point errors
			if abs(pmi[3][3] - 1.0) < epsilon:
				# pmi[3][3] is 1.0 if the 3dwin is in ortho-projection mode (toggled with numpad 5)

				# 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.

				d=Vector(list(Window.GetViewVector())+[0.0])

# all rays are parallel in ortho mode - so the direction vector is simply the viewing direction

				hms = Vector([(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+1e6*d

# 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
				
				dxy=[pm[3][3]*(((screen_x-win_min_x)/win_size_x)-1.0) - pm[3][0],
						 pm[3][3]*(((screen_y-win_min_y)/win_size_y)-1.0) - pm[3][1]]

				fp=Vector([pmi[0][0]*dxy[0]+pmi[1][0]*dxy[1],
					pmi[0][1]*dxy[0]+pmi[1][1]*dxy[1],
					pmi[0][2]*dxy[0]+pmi[1][2]*dxy[1]])

# 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=Vector([vmi[3][0],vmi[3][1],vmi[3][2],vmi[3][3]])

# the camera's location in global 3dcoords can be read directly from the inverted viewmatrix
				d=Vector(normalize_v3(sub_v3v3(p, fp))+[0.0])

# the direction vector is simply the difference vector from the virtual camera's position
#to the unprojected (screenspace) point fp

			lp = p*Matrix(myObject.getInverseMatrix())
			ld = d*Matrix(myObject.getInverseMatrix()) # normalize_v3
			return True, lp, ld	
		else:
			return False,Vector(),Vector()


def displace_vertices_on_own_normal (screen_x, screen_y):

	# DESCRIPTION:
	# ------------
	# a do-it-all function (just for speed tests...)
	# needs tweaking: the backface culling is still not working correctly in all situations
	# ...and is only for convev meshes so far...
	# ----------------------------------------------------------------------------

	global selection_size
	global myObject, workingObject, workingMesh
	global displacement
	global mirrorMode, correspVerts
	global undoDict

	mouseInView, lp,ld = getMouseLocalCoordinates(screen_x, screen_y)
	if not mouseInView:
		return

	selection = {}

	if Tablet and affsize:							
		selection_size2 = (selection_size * Tablet.Pen_GetPressure()[0])**2
	else:	         
		selection_size2 = (selection_size)**2			

	if Tablet and affdisp:						
		cur_displacement = displacement*Tablet.Pen_GetPressure()[0]			
	else:										
		cur_displacement = displacement				
	
	displacement_const = math.pi/selection_size2

	listAppended = getFaceList(screen_x, screen_y)
	for face in listAppended:
		for vertex in face.v:
			if vertex not in selection:
				l2 = cross_v3v3(ld,sub_v3v3(lp,vertex.co))
				size2 = dot_v3v3(l2,l2)

				if size2 <= selection_size2:
					if displaceMode:
						dsc=cur_displacement*(math.cos(size2*displacement_const) + 1.0)   
						vertex.co[0] += vertex.no[0] * dsc
						vertex.co[1] += vertex.no[1] * dsc
						vertex.co[2] += vertex.no[2] * dsc
						selection[vertex]=True
						vertex.sel=1
						if mirrorMode:
							try:
								tempVert = correspVerts[vertex]
								tempVert.co[0] += tempVert.no[0] * dsc
								tempVert.co[1] += tempVert.no[1] * dsc
								tempVert.co[2] += tempVert.no[2] * dsc
								selection[tempVert] = True
								tempVert.sel = 1
							except KeyError:
								pass

					elif undoMode:
						tempCo = undoDict[vertex]
						vertex.co[0] = tempCo[0]
						vertex.co[1] = tempCo[1]
						vertex.co[2] = tempCo[2]
						selection[vertex]=True
						vertex.sel=1
						if mirrorMode:
							try:
								tempVert = correspVerts[vertex]
								tempCo = undoDict[tempVert]
								tempVert.co[0] = tempCo[0]
								tempVert.co[1] = tempCo[1]
								tempVert.co[2] = tempCo[2]
								selection[tempVert]=True
								tempVert.sel=1
							except KeyError:
								pass
				else:
					selection[vertex]=False
					vertex.sel=0

	workingObject.makeDisplayList()
	workingMesh.update(1)

# ********************************************************************************

#we could use smaller grid subdivisions, which means fewer faces and verts per bin
#which means likely faster
def initializeFaceLists():
	Window.WaitCursor(1)

	selection = {}
	global myObject
	global alpha_size_x
	global alpha_size_y
	global dictFaces
	global workingMesh
	workingMesh.faces = []
	workingMesh.verts = []
	dictFaces = {}
	facedotVecDict = {}
	vectorDirtyDict = {}
	cacheVecCoMultDict = {}

	win_mid_x = win_mid_y = win_size_x = win_size_y = 0
	view3d_count = 0
	for win3d in Window.GetScreenInfo(Window.Types.VIEW3D):
		view3d_count += 1
		win_min_x , win_min_y, win_max_x, win_max_y = win3d.get('vertices')

		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
	if view3d_count > 1:
		errmsg = "too many view3d windows, only one view3d can be opened to run sculpt mesh"
		Blender.Draw.PupMenu('ERROR: %s' % errmsg)
		return True # we can only handle one view so we are done

	#testing ...
	inView, lp,ld = getMouseLocalCoordinates(0,0, True)
	facedotVec = 0.0
	viewVector = Vector(Window.GetViewVector())
	objectWorldMatrix = Matrix(myObject.getMatrix('worldspace'))
	perspMatrix = Matrix(Blender.Window.GetPerspMatrix())
	obj_mat_times_persp_mat = objectWorldMatrix*perspMatrix

	for face in myMesh.faces:
		facedotVecDict[face] = dot_v3v3(face.normal, sub_v3v3(lp, face.v[0]))
		if facedotVecDict[face] > 1e-3:
			vert_in_view = False
			for vertex in face.v:
				if vertex not in selection:
					#calculate the vertex screen coordinates...
					vectorDirtyDict[vertex.index] = vertex.co
					tempVec = vertex.co
					hvs = 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]

				vert_grid_x = vs[0]/alpha_size_x
				vert_grid_y = vs[1]/alpha_size_y
				selection[vertex] = True
				if (win_max_x >vs[0] > win_min_x) and (win_max_y > vs[1] > win_min_y):
					vert_in_view = True
			if vert_in_view:
				#here we sort into bins
				workingMesh.faces.append(face)
				for vert in face.v:
					workingMesh.verts.append(vert)
				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
			#we aren't interested in faces with a negative normal value

	####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()

	####remove the dupilcate faces and verts from workingMesh
	nd = {}
	for f in workingMesh.faces:
		nd[f] = None
	workingMesh.faces = nd.keys()

	nd = {}
	for v in workingMesh.verts:
		nd[v] = None
	workingMesh.verts = nd.keys()
	
	Window.WaitCursor(0)
	return False #we only had one view3d window so we can continue

# ********************************************************************************
def sculpt_popup_menu():
	global selection_size, min_sel, max_sel, sel_inc
	global displacement, min_dis, max_dis, dis_inc
	global mirrorMode, displaceMode, undoMode, useSubSurfMode

	global affsize, affdisp					
	
	mirrorstring = undostring = affsizestring = affdispstring = "off"	
	if mirrorMode: mirrorstring = "on"
	if undoMode: undostring = "on"	
	if affsize: affsizestring = "on"
	if affdisp: affdispstring = "on"
	
	menustring = "Interactive Paint%t|%l|Selection Size: "+str(round(selection_size,2))+"%x1|Displacement: "+str(round(displacement,3))+"%x2|%l|MirrorMode is: "+mirrorstring+"%x3|UndoMode is: "+undostring+"%x4"	
	
	if Tablet: menustring += "|%l|%l|Tablet pressure affects:|%l|Selection Size: "+affsizestring+"%x5|Displacement: "+affdispstring+"%x6"	
	
	menu_result = Draw.PupMenu(menustring) 

	if menu_result == 1:
		temp = Draw.PupFloatInput("Selection Size:", selection_size, min_sel, max_sel, sel_inc, 4)
		if temp: selection_size = temp
	if menu_result == 2:
		temp = Draw.PupFloatInput("Displacement:", displacement, min_dis, max_dis, dis_inc, 4)
		if temp: displacement = temp
	if menu_result == 3:
		mirrorMode = not mirrorMode
	if menu_result == 4:
		displaceMode = not displaceMode
		undoMode = not undoMode
	if menu_result == 5:			
		affsize = not affsize		
	if menu_result == 6:			
		affdisp = not affdisp				
	
#	if menu_result == 5:
#		useSubSurfMode = not useSubSurfMode

# ********************************************************************************

def prep_object ():
	global myMesh, workingMesh, workingObject, myObject, scene, useSubSurfMode, old_mode
	old_mode = Window.EditMode()
	Window.EditMode(0)
	myObject.copyAllPropertiesTo(workingObject)
	workingObject.loc = myObject.loc
	workingObject.drawMode = myObject.drawMode
	workingObject.drawType = myObject.drawType
	workingObject.dsize = myObject.dsize
	workingObject.rot = myObject.rot
	workingObject.size = myObject.size
	workingMesh.setSubDivLevels(myMesh.getSubDivLevels())
	if useSubSurfMode:
		workingMesh.setMode("SubSurf")
	scene.unlink(myObject)
	scene.link(workingObject)
	workingObject.Layer = myObject.Layer
	workingObject.makeDisplayList()
	workingObject.select(True)
	myObject.select(False)
	

# ********************************************************************************

def unprep_object():
	global scene, workingObject, myObject, myMesh, old_mode
	scene.unlink(workingObject)
	scene.link(myObject)
	workingObject.select(False)
	myObject.makeDisplayList()
	myObject.select(True)
	myMesh.update()
	Window.EditMode(old_mode)
	
# ********************************************************************************

def viewChanged(lastPerspMat, currentPerspMat):
	epsilon = .0001
	tempMat = currentPerspMat - lastPerspMat
	for i in range(4):
		for j in range(4):
			if abs(tempMat[i][j]) >= epsilon:
				return True
	return False

# ********************************************************************************

#finds the corresponding vertices for mirror
def FindCorrespVerts():
	epsilon = .0001
	global myMesh
	global correspVerts
	global undoDict
	global mirrorMode
	vertexDict = {}
	correspVerts = {}
	badkeys = {}
	centerVert = []
	
	FindCorrespVertsSubLoop(1000)	
	if len(badkeys) > 0 or len(myMesh.verts) > len(correspVerts) + len(centerVert):
		#print "in second loop"
		#print len(myMesh.verts)
		#print len(correspVerts)
		#print len(centerVert)
		#print len(badkeys)
		FindCorrespVertsSubLoop(1000000)
		if len(badkeys) > 0 or len(myMesh.verts) > len(correspVerts) + len(centerVert):
			print "mesh is not perfectly symmetrical so turning mirrorMode off"
			#print len(myMesh.verts)
			#print len(correspVerts)
			#print len(centerVert)
			#print len(badkeys)			
			mirrorMode = False
	#print "done loading keys"	
	

def FindCorrespVertsSubLoop(size):
	epsilon = 1.0/(size*10.0)
	global myMesh
	global correspVerts
	global undoDict
	vertexDict = {}
	correspVerts = {}
	badkeys = {}
	centerVert = []
	
	Window.WaitCursor(1)	
	for vert in myMesh.verts:
		x,y,z = vert.co
		undoDict[vert] = [x,y,z]		
		tempTuple = (int(round(x*size)), int(round(y*size)), int(round(z*size)))
		vertexDict[tempTuple] = vert
	
	for key in vertexDict:
		x,y,z = key
		tempKey = (-x, y, z)
		try:
			correspVerts[vertexDict[key]] = vertexDict[tempKey]
		except KeyError:
			if abs(key[0]) < epsilon:
				centerVert.append(key)
			else:
				badkeys[vertexDict[key]] = None
	Window.WaitCursor(0)	


# ********************************************************************************

def scaleDispAndSelection():
	global selection_size, max_sel, sel_inc
	global displacement, min_dis, max_dis, dis_inc
	
	sFactor = getScaleFactor()
	selection_size *= sFactor
	displacement *= sFactor
	if selection_size > max_sel:
		selection_size = max_sel
		print "warning for best results it is recommended that you scale the object down by %s and then 'apply rotation and scaling'" % int(sFactor)
	elif selection_size < min_sel:
		selection_size = min_sel
		temp = 2
		if sFactor > 0 : temp = int(1.0/sFactor)
		print "warning for best results it is recommended that you scale the object up by %s and then 'apply rotation and scaling'" % temp		
	if displacement > max_dis:
		displacement = max_dis
	elif displacement < +0.005:
		displacement = +0.005

# ********************************************************************************

def getScaleFactor():
	global myObject
	global baseSize
	boundingBox = myObject.getBoundBox()
	tempVert = boundingBox[0]
	boundx_min = boundx_max = tempVert[0]
	boundy_min = boundy_max = tempVert[1]
	boundz_min = boundz_max = tempVert[2]
	for v in boundingBox:
		if v[0] <= boundx_min:
			boundx_min = v[0]
		if v[0] >= boundx_max:
			boundx_max = v[0]
		if v[1] <= boundy_min:
			boundy_min = v[1]
		if v[1] >= boundy_max:
			boundy_max = v[1]
		if v[2] <= boundz_min:
			boundz_min = v[2]
		if v[2] >= boundz_max:
			boundz_max = v[2]

	avg_x = boundx_max-boundx_min
	avg_y = boundy_max-boundy_min
	avg_z = boundz_max-boundz_min

	#now we find the most representative value of the three and use that as our scale factor

	thisList = [avg_x,avg_y,avg_z]
	#print thisList
	thisList.sort()
	#print thisList
	return thisList[1]/baseSize

def updateSelectionSize(direction):
	global selection_size, min_sel, max_sel, sel_inc
	selection_size += direction*sel_inc
	if selection_size > max_sel:
		selection_size = max_sel
	if selection_size < min_sel:
		selection_size = min_sel

def updateDisplacementHeight(direction):
	global displacement, min_dis, max_dis, dis_inc
	displacement += direction*dis_inc
	if displacement > max_dis:
		displacement = max_dis
	if displacement < min_dis:
		displacement = min_dis


# ********************************************************************************

def main ():

	global selection_size
	global myMesh
	global displacement
	global myObject
	global undoMode, displaceMode, mirrorMode
	global wasinverted              
		
	currentPerspMat = None
	lastPerspMat = Matrix(Window.GetPerspMatrix())
	doRedraw = False
	done = False
	id = None
	try:
		myObject = Object.GetSelected()[0]
		id = Window.GetScreenInfo(Window.Types.VIEW3D)[0].get('id')	
	except:
		print "exiting because no object was selected or no view3d window was available"
		return
	if myObject.getType() == 'Mesh':
		myMesh = myObject.getData()
		done = initializeFaceLists()
		scaleDispAndSelection()
		FindCorrespVerts()
	else:
		return
	while not done:
		currentPerspMat = Matrix(Window.GetPerspMatrix())
		if viewChanged(lastPerspMat, currentPerspMat):
			done = initializeFaceLists()
			lastPerspMat = Matrix(currentPerspMat)
			# lastPerspMat = currentPerspMat

		evt, val = Window.QRead()

		if Tablet:				
			if Tablet.Pen_IsInverted():	
				if not wasinverted:	
					undoMode=True		
					displaceMode=False	
					wasinverted = True	
			if not Tablet.Pen_IsInverted():	
				if wasinverted:		
					undoMode=False	
					displaceMode=True	
					wasinverted = False	
								
		if evt in [Draw.MOUSEX, Draw.MOUSEY]:
			continue # speed-up: ignore mouse movement
		elif evt == Draw.ESCKEY: done = True
		elif evt == Draw.LEFTMOUSE:				
			if Window.GetKeyQualifiers() == 0:
				prep_object()
				while Window.GetMouseButtons() == 1:	
					mouse_x, mouse_y = Window.GetMouseCoords()
					displace_vertices_on_own_normal(mouse_x, mouse_y)
					Blender.Window.Redraw(Window.Types.VIEW3D)
				unprep_object()
				Blender.Window.Redraw(Window.Types.VIEW3D)

		#	elif Window.GetKeyQualifiers() != 0:
			elif Window.GetKeyQualifiers() == 48 and val:
				sculpt_popup_menu()

#			else:
#				print "we should never get here"
#				print evt
#				print val
			else:
				print Window.GetKeyQualifiers()
				continue
		elif evt == Draw.REDRAW: Blender.Redraw(-1)
		elif evt == Draw.FKEY and val: displacement *= -1.0		#Toggle displacement In/Out
		elif evt == Draw.MKEY and val: mirrorMode = not mirrorMode	#Toggle mirrorMode
		elif evt == Draw.UKEY and val:					#Toggle Undomode
			displaceMode = not displaceMode
			undoMode = not undoMode
		elif evt in [Draw.LEFTARROWKEY] and val: 			#shrink radius
			updateSelectionSize(-1.0)
		elif evt in [Draw.RIGHTARROWKEY] and val: 			#grow radius
			updateSelectionSize(+1.0)
		elif evt in [Draw.UPARROWKEY] and val:				#grow displacementHeight
			updateDisplacementHeight(+1.0)
		elif evt in [Draw.DOWNARROWKEY] and val:			#shrink displacementHeight
			updateDisplacementHeight(-1.0)
		else:
			Window.QAdd(id, evt, val)
			Window.QHandle(id)
			if(evt not in [Draw.MOUSEX, Draw.MOUSEY]): Blender.Redraw()
					

# ********************************************************************************
# ********************************************************************************

# globals:
# --------

selection_size = +0.30
max_sel = +1.00
min_sel = +.01
sel_inc = +.01
displacement = +0.025
max_dis = +.200
min_dis = -.200
dis_inc = +.005
baseSize = 4.0

old_mode = 0
undoDict = {}
correspVerts = {}
undoMode = False
mirrorMode = True
useSubSurfMode = False
displaceMode = True
affsize = True							
affdisp = True							
wasinverted = False		

maxRadiusInPixels = 15
maskOffset = [maxRadiusInPixels, maxRadiusInPixels]
alpha_size_x = alpha_size_y = 2*maxRadiusInPixels
dictFaces = {}
scene = Scene.GetCurrent()
myMesh = None
myObject = None
workingMesh = NMesh.New("temp")
workingObject = Blender.Object.New('Mesh')
workingObject.link(workingMesh)
# main:
# -----

main()

# ********************************************************************************
# ********************************************************************************


  • Dev-material.png

comments: I tried it and it works nice, 1 are you going to make a gui so u can see how much displacement you get and how big the brush is u use? maybe as usable sliders? I might eventually do a mouse pointer that shows radius of effect and displacement height, but not in the immediate future 2 is there a way to write the displacement to a displacement map so u dont have tokeep the new object but just keep the displacement map and the old object? Yes, you need to use an external tool though, the ATI normal mapper and the nmf exporter combined should accomplish what you want.