From BlenderWiki

Revision as of 22:34, 6 November 2009 by Ideasman42 (Talk | contribs)
(diff) ← Older revision | Current revision (diff) | Newer revision → (diff)
Jump to: navigation, search

[edit] How users can get features in Blender

This document is a guide for users with no knowledge of programming, to get new features added into Blender3D.


[edit] Initial Steps

Follow these steps before getting too serious, in many cases this is all you will need to do.

  • The feature you're interested in may already exist in the development version, in a branch of this code, as a script, patch in the tracker or work in progress. Before getting too far, try the search forum function on blenderartist.org, ask on IRC chat or join the bf-funboard functionality mailing list.
  • The http://www.blenderstorm.org page was started so users can suggest ideas and have others (including blender devs) give feedback, see if your idea has been requested here.
  • Your idea may be on a developers TODO list. In this case you can still help in their planning with discussion, examples and implementation suggestions.
  • If a similar feature or tool is already available, take the time to learn how to use it, maybe it can be extended to do what you want.

At this point you should know whether you need to do a lot of planning or not. Sometimes you'll suggest something and it will be in blender's subversion repository the next day, but more often then not the low hanging fruit has been picked.

A small feature may end up on a developers TODO list and you need not do any more work, but larger features will need some planning, read on!

[edit] Planning

Now that you know that your feature is non trivial and doesn't already exist you'll need to do some planning. Be prepared to spend some time talking to users and developers.

While talking and networking with interested developers, you may find you end up in the role of project manager (depending on the complexity of your feature). This takes time and effort on your part.

Be prepared to write a document describing the implementation of this feature, make necessary changes as others discuss and evaluate.

Not everybody has the necessary knowledge to plan complex features. In this case you're better off seeing if somebody else can help as a volunteer.

[edit] Considerations

  • Blender has its own way of working, just copying a feature from another application may be impossible or undesirable.
  • Blender is used for animation/sculpting/architecture/simulation/games - features in blender need to be versatile and ideally span across many work-flows, not only the usage you have in mind.
  • Be prepared for rejection, for various reasons, not all tools/features belong IN Blender, maybe it is better implemented as a script, or external tool.
  • Adding seemingly straightforward features may involve adding/rewriting or extending underlying parts of blender that weren't designed with your feature in mind. Don't assume your addition is easy.
  • Even if you feel very strongly about something, try not to come across as negative or overly pushy, most developers are volunteers, often aware of areas for improvement but need to take time to add them.

[edit] Funding Projects

Contracting developers can work on a specific feature but throwing money at a developer to write a feature doesn't work as well as you might think.

First of all, many of the Blender dev's are busy students/professionals who work on blender for fun. Accepting money is only pressure to fulfill a task they may have trouble committing time to. So money isn't necessarily that much of an incentive.

You may find a developer who is interested in accepting payment for writing a feature, this in its self isn't a total solution. We don't just want the feature by itself, we need high quality well commented code as well as somebody to maintain this feature and fix any bugs that might arise later. For this reason, asking amongst people in the blender community is the best place to start.

Make sure the feature is well specified so the developer knows exactly what to do. Choosing to be less involved may be ok but, it's far better to become involved in the planning phase and work out something with clear specifications. A developer can estimate development time to quote a price.

[edit] Code Bounty

From my experience code bounties don't work that well, even though they attract attention. You end up having to deal with developers who are not that experienced and are only interested in the money (not maintaining their work) or developers from other projects who don't understand Blender's codebase and may end up spending longer on a small project than an existing developer.

In short, you're better off finding the right person for the job and working with them to get the project complete.

If this doesn't work, a code bounty might since it has the advantage of attracting new developers. Id suggest only trying this if finding an existing developer fails.

  • Features that need new code added (smoke/flame for instance), can be done by new developers who can write this and learn Blender's internals on the way (like some Google Summer of code projects - Sculpt tool)
  • Features that require extensive knowledge of Blender's codebase are better given to existing developers.

[edit] Example Proposals

http://www.harkyman.com/animprop/caproposal.html

[edit] BuildBot Script

Automatically build and upload on svn changes, can deal with multiple branches and build options.


build_conf.py

config file for the build bot, directories to build and arguments, any changes or additional builds will be detected and built/rebuild while build_bot.py runs

BLENDER_BUILDS = {}

trunk = '/home/ideasman42/build/blender'

BLENDER_BUILDS['linux_x86-64_lite'] = {\
	'root':trunk, \
	'args':'blenderlite BF_QUIET=1 BF_FANCY=0 WITH_BF_STATICCXX=1 BF_CXX_LIB_STATIC="/usr/lib/gcc/x86_64-linux-gnu/4.3/libstdc++.a" PLATFORM_LINKFLAGS="-pthread -static-libgcc"', \
	'strip':True, \
}

BLENDER_BUILDS['linux_x86-64_lite_debug'] = {\
	'root':trunk, \
	'args':BLENDER_BUILDS['linux_x86-64_lite']['args'] + ' BF_DEBUG=1 BF_DEBUG_FLAGS="-O0 -g3 -ggdb3 -fno-inline"', \
	'strip':False, \
}

BLENDER_BUILDS['win32_lite'] = {\
	'root':trunk, \
	'args':'blenderlite BF_CROSS=1 WITH_BF_STATICCXX=1  BF_CXX_LIB_STATIC="/home/ideasman42/build/lib/windows/pthreads/lib/libpthreadGC2.a /usr/lib/gcc/i586-mingw32msvc/4.2.1-sjlj/libstdc++.a /home/ideasman42/build/lib/windows/zlib/lib/libz.a /home/ideasman42/build/lib/windows/png/lib/libpng12.a  /home/ideasman42/build/lib/windows/jpeg/lib/libjpeg.a" BF_QUIET=0  BF_PTHREADS_LIB="" BF_PTHREADS_LIBPATH=""', \
	'strip':True, 'strip_cmd':'i586-mingw32msvc-strip',\
	'compress':'zip'
}

BLENDER_BUILDS['win32_lite_debug'] = {\
	'root':trunk, \
	'args':BLENDER_BUILDS['win32_lite']['args'] + ' BF_DEBUG=1 BF_DEBUG_FLAGS="-O0 -g3 -ggdb3 -fno-inline"', \
	'strip':False, \
	'compress':'zip'
}
build_bot.py

import os
import shutil # recursive delete
import time
SLEEP = 60

WPUT_BIN = '/opt/wput/bin/wput'
WPUT_TARGET = 'ftp://autobuilds@graphicall.org:xxxxxxxx@ftp.graphicall.org/'
WPUT_ARGS= '--dont-continue --reupload'

BLENDER_BIN = 'blender'
REBUILD_FULL = True # make clean every time?
VERBOSE = False


# Better to import so we can hardlink to a chroot
# from build_conf import BLENDER_BUILDS

import build_conf

def build_rev(ID):
	for l in os.popen('svn info %s' % build_conf.BLENDER_BUILDS[ID]['root']).readlines():
		if l.startswith('Last Changed Rev: '):
			return int(l.split()[-1])
	return -1

def build_up(ID):
	'''
	return True if we changed
	'''
	
	rev = build_rev(ID)
	if VERBOSE: print 'attempting SVN update, currently at rev:', rev
	os.system('svn up %s > /dev/null' % build_conf.BLENDER_BUILDS[ID]['root'])
	if VERBOSE: print '...done'
	return rev != build_rev(ID)


	

def build_do(ID):
	txt_out = 'build_out_%s.txt' % ID
	txt_warn = 'build_warn_%s.txt' % ID
	dir_build = '../build/linux2_%s' % ID
	dir_install = '../blender-%s' % ID
	file_tar = '../blender_%s.tar' % ID
	file_zip = '../blender_%s.zip' % ID
	file_bin = os.path.join(build_conf.BLENDER_BUILDS[ID]['root'], dir_install, BLENDER_BIN)
	
	# remove old install files
	print '\tblender:', ID, '-- removing all install files'
	if os.path.exists(dir_install):
		shutil.rmtree(dir_install) #os.rmdir( dir_install )
	
	# opposed to a clean build
	if REBUILD_FULL:
		print '\tblender:', ID, '-- clean build...'
		if os.path.exists(dir_build):
			shutil.rmtree(dir_build) #os.rmdir( dir_install )
		
	# Use BF_CONFIG ?
	
	args = build_conf.BLENDER_BUILDS[ID]['args'] + ' '
	args += 'BF_FANCY=0 '
	args += 'BF_BUILDDIR=%s ' % dir_build
	args += 'BF_INSTALLDIR=%s ' % dir_install
	
	# os.system('python scons/scons.py clean')
	print '\tblender:', ID, '-- building blender...'
	os.system('python scons/scons.py %s  1> %s 2> %s' % (args, txt_out, txt_warn) )
	print "\tscons args:", args
	# os.system('python scons/scons.py %s' % args ) # for testing 
	
	# Is this a windows binary?
	if os.path.exists( file_bin + '.exe' ):
		file_bin = file_bin + '.exe'
	
	if not os.path.exists( file_bin ):
		print 'Build Failed! - Not Found:', file_bin
		return
	
	shutil.copyfile(txt_out, os.path.join( dir_install, os.path.basename(txt_out) ))
	shutil.copyfile(txt_warn, os.path.join( dir_install, os.path.basename(txt_warn) ))
	
	# strip the binary for smaller size?
	if build_conf.BLENDER_BUILDS[ID]['strip']:
		if 'strip_cmd' in build_conf.BLENDER_BUILDS[ID]:
			strip_cmd = build_conf.BLENDER_BUILDS[ID]['strip_cmd']
		else:
			strip_cmd = 'strip'
		
		os.system('%s -s "%s"' % (strip_cmd, file_bin))
	
	# Build a list of files to upload
	uploads = []
	'''
	if file_bin.endswith('.exe'): # hack for win32 blenderlite
		os.system('/opt/upx/upx --lzma -9 %s' % file_bin)
		file_bin_new = file_bin.split('/')[-1]
		file_bin_new = file_bin_new.replace('blender.exe', 'blender_' + ID + '.exe')
		os.system('mv %s %s' % (file_bin, file_bin_new))
		uploads.append(file_bin_new)
	else:
	'''
	
	if 1:
		if 'compress' in build_conf.BLENDER_BUILDS[ID] and build_conf.BLENDER_BUILDS[ID]['compress']=='zip':
			if os.path.exists(file_zip):
				os.remove(file_zip)
			
			cwd = os.getcwd()
			os.chdir(dir_install)
			os.system('zip -9 -r %s *' % (os.path.join(cwd, file_zip)))
			os.chdir(cwd)
			uploads.append(file_zip)
			
		else:
			print '\tblender:', ID, '-- tar files...'
			os.system('tar -cf %s %s' % (file_tar, dir_install))
			print '\tblender:', ID, '-- bzip2 tar files...'
	
			if os.path.exists('%s.bz2' % file_tar):
				os.remove('%s.bz2' % file_tar)
	
			os.system('bzip2 -9 %s' % file_tar)
	
			# TODO - wput
			print '\tblender:', ID, 'uploading'
			print '%s %s.bz2 %s' % (WPUT_BIN, file_tar, WPUT_TARGET)
	
			uploads.append(file_tar + '.bz2')
	
	uploads.append(txt_out)
	uploads.append(txt_warn)

	for f in uploads:
		os.system('%s %s %s %s' % (WPUT_BIN, f, WPUT_TARGET, WPUT_ARGS))
	
	print 'done...'


def build_data_str():
	'''
	Use this to see if build settings change
	'''
	build_data = {}
	for ID, value in build_conf.BLENDER_BUILDS.iteritems():
		build_data[ID] = str(value)
	
	return build_data

def main():
	# main build loop
	
	# Allow new builds to be added while the script runs
	
	build_data_prev = build_data_str()
	
	ok = True
	print "Starting build loop for", len(build_conf.BLENDER_BUILDS), 'blenders'
	while ok:
		
		# refresh settings
		reload(build_conf)
		build_data = build_data_str()
		
		# Get unique roots and link with ID's
		root_paths = {}
		for ID in build_conf.BLENDER_BUILDS: # keys()
			root = build_conf.BLENDER_BUILDS[ID]['root']
			root_paths.setdefault(root, []).append(ID)
	
		action = False
		# huston we have an update
		
		rebuild_ids = set()
		
		# add IDs from changed svn for update
		for root, ID_list in root_paths.iteritems():
			if build_up(ID_list[0]): # will be same for all builds that use this path
				rebuild_ids.update(ID_list)
		
		# add IDs with changes settings to update
		for ID in build_data:
			if ID not in build_data_prev.keys() or build_data[ID] != build_data_prev[ID]:
				rebuild_ids.add(ID)
		
		# build IDs we have tagged
		for ID in sorted(rebuild_ids):
			os.chdir( build_conf.BLENDER_BUILDS[ID]['root'] )
			build_do(ID)
			action = True
		
		build_data_prev = build_data
		
		if action:
			print ' ...sleeping, can cancel here with Ctrl+C'
		
		try:
			time.sleep(SLEEP)
		except KeyboardInterrupt:
			print 'Goodbye'
			return
		
		if action:
			print ' ...Attempting update now, hold tight'
		time.sleep(2) # some grace time.

	
if __name__ == '__main__':
	main()

[edit] Building the most compatible Linux Binary (yofrankie)

Here are the scons options I have used for yo frankie to be as compatible as possible

user-config_apricot.py

WITH_BF_GAMEENGINE = 'true'
WITH_BF_PLAYER = 'true'
BF_PYTHON_VERSION = '2.5'

WITH_BF_STATICPYTHON = 'true' # dont depend on systems libpython.so
WITH_BF_STATICCXX = 'true' # dont depend on systems libstdc++.so
WITH_BF_STATICOPENGL = 'false' # use systems opengl

WITH_BF_STATICOPENAL = 'true'

# NOTE - OpenAL-soft does not build a static lib.
# needed to make own. after building openal-soft with cmake do the following...
# ar cr openal.a `find . -name "*.o"`
# now copy openal.a to /usr/lib
BF_OPENAL_LIB_STATIC = '${BF_OPENAL}/lib/libopenal.a ${BF_OPENAL}/lib/libalut.a'

WITH_BF_OPENJPEG = 'false'
WITH_BF_OPENEXR = 'false'
WITH_BF_FFMPEG = 'false'
WITH_BF_INTERNATIONAL = 'false'
WITH_BF_ICONV = 'false'

BF_BUILDDIR="../build/linux2_apricot"
BF_INSTALLDIR="../install/linux2_apricot"

# -static-libgcc removed the dependency for libgcc_s.so
PLATFORM_LINKFLAGS = ['-pthread', '-static-libgcc']

use this config with the command

scons BF_CONFIG=user-config_apricot.py

If for some reason libstdc++ is still a dependency you can add this to your user-config.py

CC = 'gcc -Wl,--as-needed'
CXX = 'g++ -Wl,--as-needed'

[edit] Autopackage & Blender in Linux

This is a some tests making an autopackage for blender, with the steps to make your own.

if your interested in trying out the one Iv built, here it is. http://graphicall.org/builds/builds/showbuild.php?action=show&id=418

first of all, get the autopackage tools.

Youll only need the Autopackage Development Environment and APBuild. http://autopackage.org/download-tools.html


Here is the main spec file that manages building and generating the package. It needs to be saved in ./autopackage/default.apspec relative to the root of your blender source tree.

# -*- shell-script -*-
[Meta]
ShortName: Blender3D
SoftwareVersion: 2.44
DisplayName: Blender3D
RootName: @blender.org/blender:$SOFTWAREVERSION
Summary: Blender is an open source software 3D animation program.
Maintainer: ideasman42@gmail.com
Packager: ideasman42@gmail.com
PackageVersion: 1
CPUArchitectures: x86
AutopackageTarget: 1.2
Type: Application
License: GNU General Public License (GPL)
URL: blender.org
Compression: lzma # requires v1.2 of AutopackageTarget

[Description]
Blender is a 3D application with a robust feature set similar in scope and depth
to other high-end 3D software such as Softimage|XSI, Cinema 4D, 3ds Max and Maya.
These features include advanced simulation tools such as rigid body dynamics,
fluid dynamics, and softbody dynamics, modifier based modeling tools,
powerful character animation tools,
a node based material and compositing system and an embedded scripting engine based on Python.

[BuildPrepare]
# If you're using autotools, the default should be enough.
# prepareBuild will set up apbuild and run configure for you. If you
# need to pass arguments to configure, just add them to prepareBuild:
# prepareBuild --enable-foo --disable-bar

# prepareBuild assumes ./configure; make; make install
# use scons instead, disable most things to get maximum compatibility
export APBUILD_STATIC="png jpeg z python2.5"
export CFLAGS=-Os -pipe 

scons \
	CC=apgcc \
	CXX=apg++ \
	REL_CFLAGS="-Os" \
	REL_CFLAGS="-Os" \
	BF_INSTALLDIR=$build_root/build \
	BF_PYTHON_VERSION="2.5" \
	WITH_BF_OPENAL=false \
	WITH_BF_GAMEENGINE=false \
	WITH_BF_OPENEXR=false \
	WITH_FFMPEG=false \
	WITH_FREETYPE2=false \
	WITH_BF_INTERNATIONAL=false \
	WITH_BF_BULLET=false \
	WITH_BF_YAFRAY=false

# copy python scripts into our own defined PYTHONHOME
PYLIB_FROM=/usr/lib/python2.5
PYLIB_TO=$build_root/build/.blender/pymodules

# os
copyFiles $PYLIB_FROM/os.py			$PYLIB_TO/
 copyFiles $PYLIB_FROM/posixpath.py		$PYLIB_TO/
 copyFiles $PYLIB_FROM/posixfile.py		$PYLIB_TO/
 copyFiles $PYLIB_FROM/stat.py			$PYLIB_TO/
 copyFiles $PYLIB_FROM/UserDict.py		$PYLIB_TO/
 copyFiles $PYLIB_FROM/copy_reg.py		$PYLIB_TO/

copyFiles $PYLIB_FROM/random.py			$PYLIB_TO/
 copyFiles $PYLIB_FROM/warnings.py		$PYLIB_TO/
 copyFiles $PYLIB_FROM/linecache.py		$PYLIB_TO/

copyFiles $PYLIB_FROM/webbrowser.py		$PYLIB_TO/
 copyFiles $PYLIB_FROM/subprocess.py		$PYLIB_TO/
 copyFiles $PYLIB_FROM/shlex.py			$PYLIB_TO/
 copyFiles $PYLIB_FROM/traceback.py		$PYLIB_TO/

copyFiles $PYLIB_FROM/chunk.py			$PYLIB_TO/
copyFiles $PYLIB_FROM/struct.py			$PYLIB_TO/
copyFiles $PYLIB_FROM/types.py			$PYLIB_TO/
copyFiles $PYLIB_FROM/re.py			$PYLIB_TO/
copyFiles $PYLIB_FROM/sre.py			$PYLIB_TO/
copyFiles $PYLIB_FROM/sre_compile.py		$PYLIB_TO/
copyFiles $PYLIB_FROM/sre_parse.py		$PYLIB_TO/
copyFiles $PYLIB_FROM/sre_constants.py		$PYLIB_TO/
copyFiles $PYLIB_FROM/StringIO.py		$PYLIB_TO/


echo '<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
  <mime-type type="application/blender3d">
    <comment>Blender 3D file</comment>
    <magic priority="50">
      <match offset="0" type="string" value="BLENDER" />
    </magic>
    <glob pattern="*.blend*" />
  </mime-type>
</mime-info>' > $build_root/blend.xml


# copy icons, hicolor is a freedesktop standard location for icons.
copyFiles ./release/freedesktop/icons/16x16/blender.png $build_root/icons/hicolor/16x16/apps/
copyFiles ./release/freedesktop/icons/22x22/blender.png $build_root/icons/hicolor/22x22/apps/
copyFiles ./release/freedesktop/icons/32x32/blender.png $build_root/icons/hicolor/32x32/apps/
copyFiles ./release/freedesktop/icons/scalable/blender.svg $build_root/icons/hicolor/scalable/apps/

# Remove unneeded files
# Since I use blenders CVS repo for running scripts,
# there may be some files here.
rm $build_root/build/.blender/scripts/bpydata/config/*.cfg
rm $build_root/build/.blender/scripts/*.pyc
rm $build_root/build/.blender/scripts/bpymodules/*.pyc

# Warning, since we have international disabled, remove language files
# this should be removed before release
rm -rf $build_root/build/.blender/locale

[BuildUnprepare]
# If you're using prepareBuild above, there is no need to change this!
unprepareBuild

[Globals]

[Prepare]
# add some libs here SDL would be good

[Imports]
# This command will tell makeinstaller what to include in the package.
# The selection comes from the files created by 'make install' or equivalent.
# Usually, you can leave this at the default
echo '*' | import

[Install]
# See http://www.autopackage.org/api/ for details
# MYPREFIX="$HOME/.local/" # use instead of $PREFIX, so we are never installed globaly.
copyFiles build/* "$PREFIX/share/blender_dev"

# dot files dont copy??
copyFiles build/.blender/* "$PREFIX/share/blender_dev/.blender/"


# Make a shell script
# This script sets the HOME dir so we can have our own scripts.
# Keep this so you can launch from the command line with a system wide install
echo "#!/bin/sh
HOME=$PREFIX/share/blender_dev
PYTHONHOME=$PREFIX/share/blender_dev/pymodules
exec $PREFIX/share/blender_dev/blender
" > build/blender_dev # so we can use copyFile and track the change, create bin if its not there

copyFile build/blender_dev $PREFIX/bin/blender_dev
chmod +x $PREFIX/bin/blender_dev


# make a desktop file, this should be in blender alredy, HACK!
echo "[Desktop Entry]
Name=Blender3D dev
Comment=Blender is an open source software 3D animation program.
Encoding=UTF-8
Version=1.0
Type=Application
Categories=Graphics;3DGraphics;Art;
MimeType=application/blender3d			
Exec=env HOME='$PREFIX/share/blender_dev/' PYTHONHOME='$PREFIX/share/blender_dev/pymodules' '$PREFIX/share/blender_dev/blender' %F
TryExec='$PREFIX/share/blender_dev/blender'
Terminal=true
StartupNotify=true
Icon=blender.svg" > build/blender_dev.desktop

copyFile build/blender_dev.desktop $HOME/Desktop/blender_dev.desktop

installIcon icons/*
installMenuItem "Graphics" build/blender_dev.desktop
installMime blend.xml

[Uninstall]
# Leaving this at the default is safe unless you use custom commands in
# "Install" to create files. By default, all autopackage API functions are
# logged.
uninstallFromLog

in your blender root (where youd normaly run make or scons) run

makeinstaller

This will run scons with some alternative options.

if all goes well youll end up with a file "Blender3D-2.44.x86.package" in the same directory. you should be able to run this on another system with minimal chance of linking errors etc.

you can do a

chmod +x Blender3D\ 2.44.package
./Blender3D\ 2.44.package

or once you have autopackage

package install Blender3D\ 2.44.package

to install the package.

I want this to be able to coexist with an existing install of blender, so Iv made it install to """~/.local/share/blender_dev/""" and the binary is accessed from """~/.local/bin/blender_dev"""

This can be changed to suit out needs.

[edit] Static Linking

[edit] Python

Python needs to be built so libpython.a is created.

extract Python-2.5.1.tar.bz2

run

./configure --enable-shared

edit ./Modules/Setup

uncomment "#*shared*" around line 152 and rename to "*static*" The following modules are built into libpython2.5.a

uncomment: array, math, time, operator, itertools, cStringIO, cPickle, zlib, _struct, _weakref, _random, binascii, collections, fcntl, spwd, grp, select


Example:

#array arraymodule.c	# array objects

to

array arraymodule.c	# array objects

python in blender will be able to import these modules without python being on the system.

run

make
make install

Note make CC=apgcc CXX=apg++ does not work, gives errors linking at the end of the build.


Now there will be a libpython2.5.a in ./ that you can copy to /usr/lib

[edit] Known Problems (Autopackage)

1) The GUI installer dosnt work in ubuntu feisty + autopackage 1.22 (it does on my PC which is slackware), The gui loads but before installing it hangs with a progress bar moving from side to side. - With debug enabled, I see an error "Cannot open Display" - as if X is not open, is this a known problem? (installing via command line works)

[edit] SVN Masked Copy (script)

When syncing with trunk I found Id sometimes run into conflicts or have trouble merging when I really just wanted a simple copy of trunk (with the exception some directories and files that I wanted to stay unchanged for my own manual editing and merging)

The script below manages this by doing a copy from 1 branch to another, excluding 'masked' files and runs the needed svn commands to add and remove files.

import os

#----------------------------#
# About...
#
# This script only runs in linux, and requires python and svn.
# 
# I wrote it to allow updating one branch from another,
# when you only care about a small set of files (mask'ed files)
# and you just want the other files to stay in sync with 'trunk' for example.
# 
# This script matches your source tree with those files and adds/removes
# files on your disk and through SVN.
# 
# The script prints bash commands to the stdout,
# so you'll need to redirect the output see below.
#----------------------------#


#----------------------------#
# Usage...
# backup your source tree
# 	tar -cvvf" ../tree_back.tar .
# 
# run the script and direct teh outout into a shell script
#	python svn_masked_copy.py > ../tree_update.sh
# 
# run the generated script
# 	bash ../tree_update.sh
#
# commit the changes
#	svn commit .
#----------------------------#


#----------------------------#
# Config...
src = '/blender_rel/blender/'				# main repo to mirror here
dst = '/blender/pyapi_devel/pyapi_devel/'	# main repo to mirror here (except masked files)
#  ignore files/dirs containing this
ignore_containing = ('.svn', '.pyc')

# files will be masked and directories recursively
# file locations are relative to 'dst'
mask =[\
'svn_masked_copy.py',\
'.sconsign.dblite',\
'blender_scons',\
'epy_doc_gen',\
'source/blender/python/',\
'source/blender/python/BPY_extern.h',\
'source/blender/python/BPY_interface.c',\
]
mask_dst = [os.path.join(dst, f) for f in mask]
mask_src = [os.path.join(src, f) for f in mask]
#----------------------------#


def main():
	def ignore(path):
		for i in ignore_containing:
			if i in path:
				return True
		return False
	
	
	def get_files(path_root, my_mask = []):
		
		def masked(path):
			for m in my_mask:
				if path.startswith(m):
					return True
			return False
		
		all_dirs =  []
		all_files = []
		walk = os.walk(path_root)
		while walk:
			try:
				path, dirs, files = walk.next()
			except:
				break
			#print path
			if not ignore(path):
				
				# print path, dirs, files
				all_dirs.extend([os.path.join(path, e) for e in dirs if not ignore(e)])
				all_files.extend([os.path.join(path, e) for e in files  if not ignore(e)])
		
		# Mask out
		all_dirs = [e for e in all_dirs if not masked(e)]
		all_files = [e for e  in all_files if not masked(e)]
		
		# smallest dir to biggest, so making new dirs works
		all_dirs.sort(key = len)
		
		
		return all_dirs, all_files

	def swap_root(_from, _to, path_from):
		return _to + path_from[len(_from):]

	src_dirs, src_files = get_files(src, mask_src)
	dst_dirs, dst_files = get_files(dst, mask_dst)


	#print swap_root(src, dst, src_dirs[0])
	# remove any files on the destination that arnt in the source.
	
	files_removed = 0
	print '# removing files...'
	for fdst in dst_files:
		fsrc = swap_root(dst, src, fdst)
		if not os.path.exists(fsrc):
			print "svn remove " + fdst
			print "rm " + fdst
			files_removed+=1
	print
	print '# removing dirs...'
	dirs_removed = 0
	for fdst in dst_dirs:
		fsrc = swap_root(dst, src, fdst)
		if not os.path.exists(fsrc):
			print "svn remove " + fdst
			print "rm " + fdst
			files_removed+=1
	print
	print '# creating dirs...'
	dirs_created = 0
	for fsrc in src_dirs:
		fdst = swap_root(src, dst, fsrc)
		if not os.path.exists(fdst):
			print "mkdirhier " + fdst
			print "svn add " + fdst
			dirs_created+=1
	
	print
	print '# copying files...'
	files_added = 0
	for fsrc in src_files:
		fdst = swap_root(src, dst, fsrc)
		
		# THIS IS ONLY NEEDED ON DIFF - BUT COPY ALL FILES FOR NOW
		print 'cat "%s" > "%s"' % (fsrc, fdst)
		
		if not os.path.exists(fdst):
			files_added += 1
			print "svn add", fdst

	print
	print '# files removed' , files_removed
	print '# dirs removed' , dirs_removed
	print '# files created' , files_added
	print '# dirs created' , dirs_created
	print
	
	# Add dirs in the dest that dont exist.
	print '# adding these dirs'
	

if __name__ == '__main__':
	main()


[edit] SVN TimeWarp Script

Sometimes bugs appear between revisions and its tedious to work out what revision they were made.

This script building blender binaries from 1 revision to another, creating named binaries, build and svn update log files for each.

# Run this from the blender source tree you want to test with
import os
# ------------------USER OPTIOINS
blend_bin = '../build/linux2/bin/blender'
rev_old = 11100
rev_new = 11110
rev_step = 1
# ------------------END USER OPTIONS


build_count = 0
rev = rev_new
while rev > rev_old:
	rev -= rev_step
	build_count +=1
build_tot = build_count

print "Building:", build_count, 'revisions'

if rev_old > rev_new:
	rev_old, rev_new = rev_new, rev_old

build_count = 0
rev = rev_new
while rev > rev_old:
	changed = False
	new_name = 'blender_' + str(rev)
	if os.path.exists('./' + new_name):
		print new_name, 'exists!. not building'
	else:
		# check that this revision effects this branch
		os.system('svn log -r %s > ./tmp.out' % rev)
		changed = open('./tmp.out', 'r').read().strip().replace('-', '')
		os.system('rm ./tmp.out')
		if not changed:
			print 'Nothing changed this revision!, next', rev-1
		else:
			print "Building rev: %i, number %i of %i" % (rev, build_count, build_tot)
			build_log = './build_%i.log' % rev
			update_log = './update_%i.log' % rev
			print '\tupdating from svn...'
			os.system('svn update --revision %s > %s' % (rev, update_log))
			os.system('rm %s' % blend_bin) # so we know if it didnt build
			print '\tbuilding...'
			os.system('scons -j4 > %s 2>&1' % build_log)
			if os.path.exists(blend_bin):
				os.system('cp "%s" "%s"' % (blend_bin, new_name))
				print '\tdone!'
			else:
				print "build failed!"
	if changed:
		rev -= rev_step
		build_count +=1
	else:
		# dont jump a whole lot since the revision wasnt changed
		rev -= 1

[edit] SVN Branch Check (Lazy Mentor Script)

This script use useful to get a series of patches from a branch commited by a single user, ignore merges.

my_student_activity.py

BRANCH = '/media/data/soc-2008-mystudent'
STUDENT = 'mystudent'
import os
lines = os.popen('svn log --stop-on-copy --xml').readlines()

rev = auth = msg = None
totlines = 0 # rough indicator
for l in lines:
	if '<author>' in l:
		auth = l.split('<author>')[1].split('<')[0]
	elif 'revision="' in l:
		rev =  l.split('"')[1]
		# print rev
	elif '<msg>' in l:
		# print l
		msg = l.lower()
		for c in '<>_-':
			msg = msg.replace(c, ' ')
		
	elif '</logentry>' in l:
		if not (rev and auth):
			print "We have a problem"
		else:
			if auth != STUDENT:
				print auth, 'is helping out ;-)'
			else:
				if msg and ' merge ' in msg or ' merged ' in msg or ' merging ' in msg:
					print 'Ignoring merge message was:', msg
				else:
					# Act on the info we have.	
					patch = os.popen('svn diff -c ' + rev).read()
					fname = '/tmp/' + STUDENT + rev + '.patch'
					print 'writing', fname
					file = open(fname, 'w')
					file.write(patch)
					totlines += sum(ln.startswith('+') for ln in patch.split('\n'))
							
		rev = auth = msg = None

print 'Total lines added', totlines
	

[edit] Diffing 2 SVN tree's with an external diffing tool

This is problematic because SVN will add an ID to the header that differs even when the files are from the same revision. You can do this in each folder to get rid of the ID lines.

find . -type f -print0 | xargs -0r sed -i 's/\$Id:.*\$/\$Id\$/'

[edit] Render API User interface (proposal)

Here is my suggestions for how a render plugin could have a user interface.

  • The render api is written as a C plugin. (The C api is being worked on as a GSOC project).
  • Render specific settings could be taken from materials/scenes/worlds/objects ID-Properties - this allows arbitrary settings to attach to any blenders data that has an ID. When ID-Properties are not available blenders would be converted.
  • Render API would read(only) from these ID-Properties, if some are out of range or the wrong type, would need to clamp the settings or use default values.

So now we need a way to change these ID-Properties.

This extends on what Matt Ebb proposed (details how it can be done)

Materials can be...

  • blender materials - the render plugin will need to convert blenders settings where possible.
  • render specific materials - materials that have their own ID-Properties (as defined by the render API plugin)

The option to use render specific settings can be a material flag....

  • The button to set this option is only available when a render plug-in is selected as render output.
  • Once enabled, the C-plugin must return the filename of a Python script to run, this script will be able to create panels that edit the ID-Properties on the material, that the render API needs.

This means each plugin has 2 parts

  • C-plugin interfaces with the rendering engine and does all the grunt work.
  • Python script that draws the user interface to adjust settings the C-Plugin makes use of.

This has advantages and disadvantages...

[edit] Pros

  • Render plugins wont need a python user interface initially, though this limits them to blenders settings (from a users perspective)
  • The plugin developer can focus on the render API and use the generic ID-Property editor to test render specific options, without writing a special UI in python.
  • Once the render plugin is finished, another developer could add the user interface.

[edit] Cons

  • To write a complete plugin you need to know 2 API's and 2 languages.
  • Each plugin will be at least 2 files.


[edit] Where to start?

For this to work, the main requirement is that the python API can create panels.

integrating in other areas is not that hard.

[edit] Peach System Setup

Moved to http://wiki.blender.org/index.php?title=Bf-institute


[edit] Development Tools

[edit] Scripts

[edit] mmeld

Multi-Meld is a python script to run meld diff viewer on all files in svn that have been changed, or can take file args.

#!/usr/bin/python
import sys, os

def svndiff(f):
	if os.path.exists(f):
		# release/scripts/ui/.svn/text-base/space_text.py.svn-base
		f_other = os.path.join(os.path.dirname(f), ".svn/text-base", os.path.basename(f) + ".svn-base" )
		os.system("meld \"%s\" \"%s\" &" % (f_other, f))

files = sys.argv[1:]

if not files:
	files = os.popen('svn st | grep "^M" | cut -f7 -d" "').read().split()

for f in files:
	svndiff(f)

[edit] touchdiff

Sometimes you want to know if any of the files you edited caused errors. Rebuilds you may have missed some so touching all modified files, then rebuilding is useful for this.

touch `svn st | grep "^M" | cut -f7 -d" "`

[edit] crep

search *.c* and *.h* files for some text, prints out numbered results, so you can type in the number to open a text editor at that line.

crep "Some words"
crep some_func
#!/bin/bash

OUT=$(mktemp)
find . -name "*.[c|h]*" -print | grep -v "\.svn" | grep -v "~" | sort | xargs grep -n --color=never -H "$@" >  $OUT

# Print text with line numbers
sed = $OUT | sed 'N;s/\n/\t/'

echo -n "enter a number to edit > "
read -e LINE

if [ "$LINE" == "" ] ; then
        echo "nothing selected... exiting."
        exit 0
fi

LINE=$LINE"p" # 4 -> 4p - for sed
EDIT=$(cat $OUT | sed -n $LINE)         # file to edit

rm $OUT

FILE=$(echo $EDIT | cut -d":" -f1)
NUM=$(echo $EDIT | cut -d":" -f2)

# Now edit the file in scite or vim
# scite $FILE -goto:$NUM &
# vim $FILE +$NUM
gedit $FILE +$NUM

Example output

ideasman42@cambo:/b$ crep BPY_call_importloader
1	./source/blender/python/BPY_interface.c:2694:int BPY_call_importloader( char *name )
2	./source/blender/python/BPY_interface.c:2696:	printf( "In BPY_call_importloader(name=%s)\n", name );
3	./source/blender/blenkernel/intern/exotic.c:2430:					if (BPY_call_importloader(name)) {
4	./source/blender/blenkernel/bad_level_call_stubs/stubs.c:245:int BPY_call_importloader(char *name)
5	./source/blender/python/BPY_extern.h:121:	int BPY_call_importloader( char *name );
6	./source/blender/blenkernel/BKE_bad_level_calls.h:149:int BPY_call_importloader(char *name);
enter a number to edit >

[edit] cedit

Search for a file from the current directory and open it at a line number, especially nice to copy/paste from compiling errors.

cedit somefile.c:255
#!/bin/bash
FILE=$(echo $1 | cut -d":" -f1)
NUM=$(echo $1 | cut -d":" -f2)
if ! [ -f $FILE ]
then
        FILE=`find -name "$FILE" | head -n1`
fi

# scite $FILE -goto:$NUM
# vim $FILE +$NUM
gedit $FILE +$NUM


[edit] pyrename

Do you ever want to rename many files without having to use GUI apps? Pyrename is a simple script that takes your file names and puts them in a text file. You can then search/replace, regex, whatever you want to the list of names. Save and close the text editor and the files will be renamed.

just cd into the directory where you want to rename files and type

pyrename .
#!/usr/bin/python
import os
import sys

def main():
	if len(sys.argv) == 1:
		print 'Give a dir to search through, no action taken.'
		return
	
	ls = []
	
	argv = sys.argv[1:]
	
	i = 0
	while i+1 < len(argv):
		if argv[i].endswith('\\'):
			argv[i] = argv[i][ :-1] + argv[i]
			argv.pop(i+1)
		else:
			i+=1
	
	cwd =  os.getcwd()
	
	for dir in argv:
		if dir.startswith('./'):
			dir = cwd + os.sep + dir[2:]
		
		# ROOT, DIRS, FILES
		pathWalk = os.walk(dir)
		pathls = [True]
		
		matchls = [] # Store a ls of (match, size), choose the biggest.
		while True:
			try:
				pathls  = pathWalk.next()
				for file in pathls[2]:
					path = pathls[0]
					if not path.endswith(os.sep):
						path += os.sep
					ls.append(path + file)
			except:
				break
	
	if not ls:
		print 'No files found, no action taken.'
		return
	
	filename = os.tmpnam()
	file = open(filename, 'w')
	
	for l in ls:
		if '\n' in l[:-1]:
			print 'Newline found in filename "%s", aborting, no action taken' % l
			return
			
	
	for l in ls:
		file.write('%s\n' % l.split(os.sep)[-1])
	file.close()
	
	os.system('$EDITOR %s' % filename)
	
	file = open(filename, 'r')
	editedLines = [l.strip() for l in file.readlines()]
	file.close()
	
	if len(editedLines) != len(ls):
		print 'Edited rename file has different number of lines. aborting, no action taken'
		os.remove(filename)
		return
	
	renamedCount = 0
	nonRenamedCount = 0
	errorCount = 0
	for orig, new in zip(ls, editedLines):
		new = os.path.dirname(orig) + os.sep + new
		if new == orig:
			print 'not renaming "%s"' % orig
			nonRenamedCount +=1
		else:
			try:
				os.rename(orig, new)
				renamedCount +=1
				print '\trenaming "%s" to "%s"' % (orig, new)
			except:
				print '\terror renaming "%s" to "%s"' % (orig, new)
				errorCount +=1
	
	os.remove(filename)
	
	print '*** renamed %i files, %i untouched, %i errors. ***' % (renamedCount, nonRenamedCount, errorCount)
	
if __name__ == '__main__':
	main()


[edit] bfbranch

an easy way to branch, Theeth wrote this (dont overwrite existing branches!). adding here for convenience.

#! /bin/sh
if [ $# -ne 2 ]; then
	echo 1>&2 Usage: $0 branch log
	exit 127
fi

svn copy https://svn.blender.org/svnroot/bf-blender/trunk/blender https://svn.blender.org/svnroot/bf-blender/branches/$1 -m "$2"

[edit] Program Execution Tracing

Sometimes its useful to know the flow of an application without having to step through each line manually. In cases where similar input makes blender act differently, it can be useful to find out in what part of the code it branches into different areas.

This can be done by running gdb, executong commands from a file, redirecting the output to a file.

gdb_flow.txt

set pagination off
break main
r  -b /home/ideasman42/test.blend -a
while 1
  step
end
  • insert your own breakpoint at the point before blender starts acting unpredictably.
  • run blender with whatever args you want.
gdb ./blender --command gdb_flow.txt > ./log_1.txt

Now run the program twice with different input and run diff on the output, I use meld, a visual diffing tool. The memory locations will be different but you can still see where large parts of the flow changes.

Note after writing this I found a similar method that I could not get working http://www.brain-dump.org/blog/entry/101/comments

[edit] define lint

This script finds defines in headers and prints them out in order of how many lines use them. Warning - this script is not that nicely written. only runs on a unix system.

import os
BLENDER = '/b'

# for a single header file
# headers = ['%s/source/blender/include/blendef.h' % BLENDER]

headers = os.popen('find %s/source -name "*.h" -print | grep -v "\.svn" | grep -v "~"' % BLENDER).readlines()
headers.sort()

for head in headers:
	head = head.strip()
	if not head or not os.path.exists(head):
		print '\tnot found', head
		continue
	
	header_text = open(head, 'rU').read()
	
	defs = []
	for i, l in enumerate(header_text.split('\n')):
		l = l.strip()
		if l.startswith('#define'):
			d = l.split()[1].split('(')[0]
			if not d.endswith('_H'):
				defs.append((d, i, l))

	defs = list(defs)
	defs_count = []

	for i, define in enumerate(defs):
		used_count = len(os.popen('find %s/ -name "*.[c|h]*" -print | grep -v "\.svn" | grep -v "~" | xargs grep -n --color=never -H "%s"' % (BLENDER, define[0])).readlines())
		
		used_count-=1 # exclude the define as a user
		
		# We may want to ignore case statements?
		defs_count.append((used_count, define))
	
	# defs_count.sort()
	output = []
	for users, define in defs_count:
		if users == 0: # may also want to check for 1 user
			d, line, fulldef = define
			# print head, users, d, line
			text = '%s:%s\t%s' % (head, line, fulldef)
			output.append(text)
	
	for t in output:
		print t

[edit] Filtering Intel C++ Compiler warnings

Intels compiler gives some useful warnings not found with GCC, heres a way to filter them for better review.

l = open('/root/icc.tmp', 'r').read()
l = l.split('\n\n')

new = []

for a in l:
	first = a.split('\n')[0]
	if 'remark' in first:
		if 'may lose significant bits' in first:											continue
		if 'floating-point equality and inequality comparisons are unreliable' in first:	continue
		if 'argument is incompatible with corresponding format string conversion' in first:	continue
		if 'is not virtual' in first:										continue
		if 'access control not specified ("public" by default)' in first:	continue
		if 'zero used for undefined preprocessing identifier' in first:		continue
		if 'operands are evaluated in unspecified order' in first:			continue
		if 'was never referenced' in first and ('self' in first or 'unused' in first or 'closure' in first or 'dummy' in first or '"arg"' in first):			continue
		
		# may want this but not now, these can be made static
		if 'external declaration in primary source file' in first:			continue
		if 'external function definition with no prior declaration' in first:			continue
		if 'old-style parameter list (anachronism)' in first:			continue
		if 'extra ";" ignored' in first:			continue
		
		
		tag = first.split(':')[-1]
		# print tag
		new.append( (tag, a))



new.sort()
for tag, a in new:
	print a 

[edit] Using Splint Static C Source Checking

#!/usr/bin/python
# gcc_splint.py
# 
# First capture the output of building blender to get all the C files with their includes
#  scons BF_QUIET=0 BF_FANCY=0 > log.txt
# for other projects this should work.
#  make > log.txt 

# Run splint on every C file...
# python gcc_splint.py log.txt


COMPILER = 'gcc'
OUT = 'splint_warn.txt'

SPLINT_ARGS = '-weak -posix-lib -linelen 10000  +ignorequals +relaxtypes -retvalother +matchanyintegral +longintegral +ignoresigns -nestcomment -predboolothers -ifempty -unrecogcomments'

# -forcehints

# We may want to remove these later
SPLINT_ARGS += ' -type -fixedformalarray -fullinitblock -fcnuse -initallelements -castfcnptr'

# These can be useful but clutter a lot
# SPLINT_ARGS  +=  ' -bufferoverflowhigh -unrecog -varuse'

# these can be useful but clutter too
# SPLINT_ARGS   += ' -syntax -namechecks '

import sys, os
argv = sys.argv

f = open(sys.argv[-1])
#f = open('log.txt')


splint_commands = []

os.system('echo "" > %s' % OUT)

for l in f:
	l = l.split()
	
	if COMPILER not in l:
		continue
	
	c_files = [f for f in l if f.lower().endswith('.c')]
	inc_dirs = [f for f in l if f.startswith('-I')]
	defs = [f for f in l if f.startswith('-D')]
	for c in c_files:
		cmd = 'splint %s %s %s %s 2>&1 >> %s' % (SPLINT_ARGS, c, ' '.join(inc_dirs), ' '.join(defs), OUT) 
		splint_commands.append(cmd)
	
	
splint_commands.sort()


for cmd in splint_commands:
	print cmd
	os.system(cmd)

[edit] Screencasting from linux

Start Blender at a lower resolution

./blender -p 0 0 800 600

Run record my desktop, will create "out.ogg" using a very high bitrate so re-compressing dosn't look bad.

recordmydesktop -v_bitrate 2000000 --full-shots -fps 10 --overwrite  -windowid $(xwininfo | grep "Window id:" | sed -e "s/xwininfo\:\ Window id:\ // ;s/\ .*//")

Press Ctrl+C in the terminal when your done.

[edit] Editing the Video in Blenders sequencer

If all you need is the video unedited you can ignore this part.

Export for editing in blender, even though blenders ffmpeg can read many formats, this one streams best.

mencoder out.ogg -oac pcm -ovc lavc -lavcopts vcodec=mjpeg:vbitrate=10000 -af resample=44100:channels=1 -o out.avi

Render to high Quality 100 AVI-Jpeg from blender and MIXDOWN the audio from the sequencer panel.

You may want to edit the audio, Audacity is good for noise reduction, compressing and normalizing.

Mux these with...

mencoder export.avi -ovc copy -oac copy -audiofile export.wav -o export_mux.avi

Compress any video to ogg for the web

ffmpeg2theora --optimize --channels 1 --samplerate 22050 --audioquality 5 out.ogg -o out_web.ogg

[edit] Batch Converting OGG to WAV

for f in *.ogg ; do mplayer -quiet -vo null -vc dummy -ao pcm:waveheader:file="$f.wav" "$f"  ; done

[edit] Development Tools Wishlist

  • Valgrind frontend that can filter output based on library names (could be written in python), since suppression files get out of date.
  • SVN Time Warp tool - Imagine SVN Blame (annotation) with a slider to step to different revisions.
  • Python module to control GCC - so one could write very comprehensive conditions for offline debugging. (Tried to write this but GCC's input is NOT easy to automate)
  • Utility to change an svn checkouts user and/or URL, for now this is ok...
    perl -pi -e 's/oldbob/newjim/g' `find | grep "\.svn/entries"`

[edit] BGE Logic Bricks Id like to write/modify

  • DONE! - Constrain Vecocity (local or global, could be used to fake anisotropic friction)
  • DONE! - Angular velocity for add object actuator (add get/setAngularVeclocity to py api)
  • DONE! - property actuator have a toggle option to toggle values, currently doing this requires a few logic bricks,
  • state actuator could have 3 state button so some states could be ignored and not set for copy actuator.
  • track actuator Time setting dosnt keep camera Z axis up.
  • set local loc/size/rot actuator (can be done with ipo's but thats a bit of a hack)
  • State sensor, so you could sens if some state was enabled.

[edit] BGE Nodal Logic

From discussions with Benoit

Some background information first. In the current implementation, sensors, controllers and actuators are executed separately: first the active sensors, which eventually generate pulse (positive or negative) to controllers. Then the triggered controllers are executed, which eventually generate pulses (positive or negative) to actuators. Then the active actuators are executed and the whole cycle restarts. Actuators are similar to controllers except that they can stay active and execute each frame even without receiving a trigger.

This architecture is easy to program but not easy to use. Normally it should be the opposite and that's why we want to introduce nodal logic. A logic graph allows to visualize a whole logic setup much better than logic bricks.

This proposal is an attempt to bring nodal logic with minimal change in the BGE code. In particular we want to keep the existing C++ sensor and actuator code because they execute complex operations at C++ speed.

A nodal logic is made of interconnected nodes. All nodes have inputs and outputs. There are 2 types of input: logic and data. Logic inputs are used to receive pulses from other nodes. There can be two types of logic inputs: start and stop. Receiving a pulse on start will cause the node to execute. Receive a stop will cause the node to stop executing. The stop input is only present on nodes that have persistent actions (actuators). The data input will be of type vector, integer, boolean, string, object reference, etc, depending on the type of node. The data input is only used when the node is activated. There are also 2 types of outputs: logic and data. Logic output are used to emit pulses. There are two cases where a node can emit a pulse. If the node is a sensor, it can create a pulse to one of its two outputs: a positive output when the internal state becomes true and a negative output when the internal state becomes false. Other types of node have only one logic output and they emit a pulse only when they finish their execution. Data output can be of type vetor, integer, string, reference, etc, depending on the node.

A graph of nodes can be grouped and become a single node for a higher level of logic.

We will define 3 types of nodes:

  • sensor nodes.

These are the only node that can produce pulses. They have 2 logic inputs to explicitely start and stop them. Starting a sensor node means that it will execute on each frame and produce pulse if the internal state changes. It's important to be able to stop sensors to save CPU when they are not needed anymore. Sensor nodes have 2 logic output as explained above. They are executed only once per frame before any other types of node.

  • atomic action node

These nodes are never active at the start of a frame. They can only be active if they receive a pulse from a sensor node or from another action node. Atomic action nodes have only one logic input and one logic output. If they receive a pulse on the input, they execute immediately and release the pulse on the output. Actions nodes can be chained: the chain of nodes is executed immediately in the same frame until the chain is broken or until it reaches a persistent action node or a sensor node. Beware that an infinite loop of atomic action node can be done, which will hang the game engine.

  • persistent action node

These nodes are similar to atomic action node except that they execute only once per frame. Execution of persistent action nodes is delayed after all atomic action nodes. A persistent action node has 2 logic inputs (for start/stop) and one logic output. The logic output produces a pulse when the persistent action is stopped. The pulse can be passed to another node and is handled on the next frame.

[edit] Implementation

These 3 types of node match closely the sensor, controller and actuator bricks that we have today so that little change will be necessary to implement them.

Starting/stopping sensors is currently done based on the number of links to controllers. An explicit method must be implemented.

C++ controllers will be removed and replaced by a variety of python node that implement the same functions: AND, OR, etc. All these nodes will be atomic action nodes. A lot of different python node can be predefined: for loop, condition, etc. A generic python node with configurable data inputs and outputs should be implemented to allow custom atomic nodes. This will replace the python controller. The advantage of having configurable inputs means that it's no longer necessary to retrieve reference in the script: the inputs will be set prior calling the node and readily available as variables in the script. It seems appropriate to implement the python node as a method of an class that has data members corresponding to the input and outputs of the node: this way the outputs are available to other nodes even after the script has executed.

C++ actuators remain the same. The actuators that are non persistent by nature will be defined as atomic action nodes, while the persistent actuators will be defined as persistent action nodes.

The execution of the graph can be done by converting it to a python script. Python is well suited to implement data connections between nodes: data inputs and outputs will simply be attributes of sensors/action nodes and easily accessible to python. Execution of sensors and persistent nodes will be left to C++, python will only be used to execute the atomic actions. When the pulse gets to a persistent action, python will simply register the action for execution at the end of the logic frame. Question: how a complex tree of actions can be converted in a python script.

Since the logic bricks are attached to objects, nodes will also be attached to objects. A graph mixing nodes of different objects should be possible. When a group of objects are linked in logic graph and this group is instantiated, the nodes must be instantiated too and the script corresponding to the graph must use references to the instance objects. Question: how the script will acquire the reference to the instance objects.


[edit] BGE Python Logic

Heres an idea I had about how developers who prefer to bypass logic bricks could do so...

Currently we have event managers (timer, joystick, keyboard, collision). Each event manager has a list of active sensors

My proposal to bypass logic brick sensors is to define a new type of sensor class that is only accessed from python with no need to have a logic brick in the UI or be connected to a controller.

[edit] Event Queue?

From the EventManager it will look like a normal sensor in the list however rather then trigger a controller, the sensor will collect a list of events in a typical event queue. This can be a python list containing any relevant info like the key pressed, location of a ray-hit. Collision-Points when 2 objects touch.

This sensor would need to be enabled (probably via python), otherwise it would be a waste to have this on when not used.

GameLogic.EventLogEnable('keyboard')
...
evens = GameLogic.Events # a normal python list
while evens:
    event= events.pop()
    if event.type == 'keyboard' and event.key == 'a':
        print("blah blah")

[edit] Event Callbacks?

Rather then logging an event queue alternately we could also register python functions to be called on spesific event types. so you could have a function to be called on any collision, keypress etc. This has the advantage that you dont need to run python every logic tick to peek into the event queue.

def MyFunc(event):
    if event.type == 'keyboard' and event.key == 'a':
        print("blah blah")

GameLogic.EventRegister('keyboard', myFunc)

You could ofcourse register multiple callback functions for each event type, or even use the same function for collision and timer callbacks for eg.

Its also a bit nicer that you only register the callbacks rather then telling python to start logging an event queue.


[edit] User documentation editing

  • Menu item for editing docs
  • Built in operator for description editing, (can be python)
    • XML/RPC submission from python (Mindrones)
  • Blog voting system, acceptance etc... (mindrones)
  • Automatic extraction of accepted descrptions (Mindrones)
  • Apply this as a patch to C/Python code + commit (me)