← Back to team overview

yade-dev team mailing list archive

[Branch ~yade-dev/yade/trunk] Rev 2361: 1. Add noqt3 feature (still keeps OpenGL available), run qt4 app at the beginning (emits warning)

 

------------------------------------------------------------
revno: 2361
committer: Václav Šmilauer <eudoxos@xxxxxxxx>
branch nick: trunk
timestamp: Tue 2010-07-13 21:58:14 +0200
message:
  1. Add noqt3 feature (still keeps OpenGL available), run qt4 app at the beginning (emits warning)
  2. Make GLUtils independent of QGLViewer
  3. Add some metadata to attribute docstrings
  4. Add scripts to render scene to pyQGLViewer and to show basic serialization interface in PyQt4.
  5. Make static attributes non-static in python (enables docstrings)
added:
  scripts/test/qt4-attributes.py
  scripts/test/qt4-pyqglviewer.py
modified:
  SConstruct
  core/SConscript
  core/main/main.py.in
  doc/sphinx/bib2rst.py
  doc/sphinx/conf.py
  doc/sphinx/yadeSphinx.py
  gui/SConscript
  lib/SConscript
  lib/opengl/GLUtils.cpp
  lib/opengl/GLUtils.hpp
  lib/serialization/Serializable.hpp
  pkg/common/RenderingEngine/OpenGLRenderingEngine.hpp
  py/yadeWrapper/customConverters.cpp
  scripts/test/chained-cylinder-spring.py


--
lp:yade
https://code.launchpad.net/~yade-dev/yade/trunk

Your team Yade developers is subscribed to branch lp:yade.
To unsubscribe from this branch go to https://code.launchpad.net/~yade-dev/yade/trunk/+edit-subscription
=== modified file 'SConstruct'
--- SConstruct	2010-07-11 18:03:54 +0000
+++ SConstruct	2010-07-13 19:58:14 +0000
@@ -139,7 +139,7 @@
 	BoolVariable('optimize','Turn on heavy optimizations',defOptions['optimize']),
 	ListVariable('exclude','Yade components that will not be built','none',names=['gui','extra','common','dem','lattice','snow']),
 	EnumVariable('PGO','Whether to "gen"erate or "use" Profile-Guided Optimization','',['','gen','use'],{'no':'','0':'','false':''},1),
-	ListVariable('features','Optional features that are turned on','log4cxx,opengl,gts,openmp,vtk',names=['opengl','log4cxx','cgal','openmp','gts','vtk','python','gl2ps','devirt-functors','never_use_this_one']),
+	ListVariable('features','Optional features that are turned on','log4cxx,opengl,gts,openmp,vtk',names=['opengl','log4cxx','cgal','openmp','gts','vtk','python','gl2ps','devirt-functors','noqt3','never_use_this_one']),
 	('jobs','Number of jobs to run at the same time (same as -j, but saved)',2,None,int),
 	#('extraModules', 'Extra directories with their own SConscript files (must be in-tree) (whitespace separated)',None,None,Split),
 	('buildPrefix','Where to create build-[version][variant] directory for intermediary files','..'),
@@ -359,12 +359,13 @@
 		ok=conf.CheckLibWithHeader('glut','GL/glut.h','c++','glutGetModifiers();',autoadd=1)
 		# TODO ok=True for darwin platform where openGL (and glut) is native
 		if not ok: featureNotOK('opengl')
-		ok=conf.CheckQt(env['QTDIR'])
-		if not ok: featureNotOK('opengl','Building with OpenGL implies qt3 interface, which was not found, although OpenGL was.')
-		env.Tool('qt'); env.Replace(QT_LIB='qt-mt')
-		ok=conf.CheckLibWithHeader(['qglviewer'],'QGLViewer/qglviewer.h','c++','QGLViewer();',autoadd=0)
-		if not ok: featureNotOK('opengl','Building with OpenGL implies the QGLViewer library installed (libqglviewer-qt3-dev package in debian/ubuntu)')
-		env['QGLVIEWER_LIB']='qglviewer-qt3';
+		if not 'noqt3' in env['features']:
+			ok=conf.CheckQt(env['QTDIR'])
+			if not ok: featureNotOK('opengl','Building with OpenGL implies qt3 interface, which was not found, although OpenGL was.')
+			env.Tool('qt'); env.Replace(QT_LIB='qt-mt')
+			ok=conf.CheckLibWithHeader(['qglviewer'],'QGLViewer/qglviewer.h','c++','QGLViewer();',autoadd=0)
+			if not ok: featureNotOK('opengl','Building with OpenGL implies the QGLViewer library installed (libqglviewer-qt3-dev package in debian/ubuntu)')
+			env['QGLVIEWER_LIB']='qglviewer-qt3';
 	if 'vtk' in env['features']:
 		ok=conf.CheckLibWithHeader(['vtkCommon'],'vtkInstantiator.h','c++','vtkInstantiator::New();',autoadd=1)
 		env.Append(LIBS='vtkHybrid')

=== modified file 'core/SConscript'
--- core/SConscript	2010-06-07 14:56:29 +0000
+++ core/SConscript	2010-07-13 19:58:14 +0000
@@ -56,7 +56,8 @@
 			'yade-support',
 			'rt', # realtime lib, for clock_gettime 
 			]+
-			(['qglviewer-qt3','yade-opengl'] if 'YADE_OPENGL' in env['CPPDEFINES'] else [])
+			(['qglviewer-qt3'] if not 'noqt3' in env['features'] else [])+
+			(['yade-opengl'] if 'opengl' in env['features'] else [])
 			,
 	)
 ])

=== modified file 'core/main/main.py.in'
--- core/main/main.py.in	2010-06-18 13:02:01 +0000
+++ core/main/main.py.in	2010-07-13 19:58:14 +0000
@@ -40,6 +40,14 @@
 
 sys.stderr.write('Welcome to Yade '+version+'\n')
 
+if 'opengl' in features and 'noqt3' in features:
+	try:
+		from PyQt4 import QtGui
+		qapp=QtGui.QApplication(sys.argv)
+		import thread
+		thread.start_new_thread(qapp.exec_,()) # this will warn, but I don't know a way around that
+	except ImportError: pass
+
 # initialization and c++ plugins import
 import yade
 # other parts we will need soon
@@ -69,13 +77,14 @@
 # run servers
 yade.system.runServers()
 
-# open GUI if possible
-qtEnabled=False
-if not opts.nogui:
-	try:
-		import yade.qt
-		qtEnabled=True
-	except ImportError: pass
+if not 'noqt3' in features:
+	# open GUI if possible
+	qtEnabled=False
+	if not opts.nogui:
+		try:
+			import yade.qt
+			qtEnabled=True
+		except ImportError: pass
 
 
 # prepare nice namespace for users
@@ -106,7 +115,7 @@
 	if 1:
 		from IPython.Shell import IPShellEmbed
 		ipshell=IPShellEmbed(
-			argv=[],
+			argv=['-q4thread'],
 			#exit_msg='Bye.',
 			banner='[[ ^L clears screen, ^U kills line.'+(' F12 controller, F11 3d view, F10 both, F9 generator, F8 plot.' if qtEnabled else '')+' ]]',
 			rc_override=dict( # ipython options, see e.g. http://www.cv.nrao.edu/~rreid/casa/tips/ipy_user_conf.py

=== modified file 'doc/sphinx/bib2rst.py'
--- doc/sphinx/bib2rst.py	2010-07-05 08:16:58 +0000
+++ doc/sphinx/bib2rst.py	2010-07-13 19:58:14 +0000
@@ -71,7 +71,7 @@
 		## ReST uses <..> to delimit URL, therefore < and > must be encoded in the URL (http://www.blooberry.com/indexdot/html/topics/urlencoding.htm)
 		def escapeUrl(url): return url.replace('<','%3c').replace('>','%3e')
 		if i.has_key('doi'): line+=' DOI `%s <http://dx.doi.org/%s>`_'%(i['doi'],escapeUrl(i['doi'])) 
-		if i.has_key('url'): line+=' `(fulltext) <%s>`_'%escapeUrl(i['url'])
+		if i.has_key('url'): line+=' `(fulltext) <%s>`__'%escapeUrl(i['url'])
 		if i.has_key('note'): line+=' (%s)'%i['note']
 		ret.append(line)
 	return [l.replace('@tilde@','~') for l in ret]

=== modified file 'doc/sphinx/conf.py'
--- doc/sphinx/conf.py	2010-07-12 09:11:16 +0000
+++ doc/sphinx/conf.py	2010-07-13 19:58:14 +0000
@@ -108,11 +108,15 @@
 def ydefault_role(role,rawtext,text,lineno,inliner,options={},content=[]):
 	"Handle the :ydefault:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing."
 	return [],[]
+def yattrtype_role(role,rawtext,text,lineno,inliner,options={},content=[]):
+	"Handle the :yattrtype:`something` role. fixSignature handles it now in the member signature itself, this merely expands to nothing."
+	return [],[]
 
 from docutils.parsers.rst import roles
 roles.register_canonical_role('yref', yaderef_role)
 roles.register_canonical_role('ysrc', yadesrc_role)
 roles.register_canonical_role('ydefault', ydefault_role)
+roles.register_canonical_role('yattrtype', yattrtype_role)
 
 
 ## http://sphinx.pocoo.org/config.html#confval-rst_epilog
@@ -125,12 +129,8 @@
 
 
 def customExclude(app, what, name, obj, skip, options):
-	if obj=='<unbound method Serializable.clone>':
-		print 1000*'@'
-		return False
-	if re.match(r'\bclone\b',name):
-		#if 'Serializable' in name: print 1000*'#',name
-		#print 1000*'#',name, obj, what
+	if name=='clone':
+		if 'Serializable.clone' in str(obj): return False
 		return True
 	if hasattr(obj,'__doc__') and obj.__doc__ and ('|ydeprecated|' in obj.__doc__ or '|ynodoc|' in obj.__doc__): return True
 	#if re.match(r'\b(__init__|__reduce__|__repr__|__str__)\b',name): return True
@@ -167,6 +167,7 @@
 	# remove empty default roles, which is not properly interpreted by docutils parser
 	for i in range(0,len(lines)):
 		lines[i]=lines[i].replace(':ydefault:``','')
+		lines[i]=lines[i].replace(':yattrtype:``','')
 		#lines[i]=re.sub(':``',':` `',lines[i])
 	# remove signature of boost::python function docstring, which is the first line of the docstring
 	if isBoostFunc(what,obj):
@@ -233,6 +234,7 @@
 			# grab the return value
 			try:
 				sig=') -> '+sig.split('->')[-1]
+		#if 'Serializable' in name: print 1000*'#',name
 			except IndexError:
 				sig=')'
 	return '('+sig,strippedDoc

=== modified file 'doc/sphinx/yadeSphinx.py'
--- doc/sphinx/yadeSphinx.py	2010-07-12 17:36:42 +0000
+++ doc/sphinx/yadeSphinx.py	2010-07-13 19:58:14 +0000
@@ -131,7 +131,7 @@
 	sect('Constitutive laws','',['LawFunctor','LawDispatcher'])+
 	sect('Callbacks','',['BodyCallback','IntrCallback'])+
 	sect('Preprocessors','',['FileGenerator'])+
-	sect('Rendering','',['OpenGLRenderingEngine','GlShapeFunctor','GlStateFunctor','GlBoundFunctor','GlInteractionGeometryFunctor','GlInteractionPhysicsFunctor','GlShapeDispatcher','GlStateDispatcher','GlBoundDispatcher,','GlInteractionGeometryDispatcher','GlInteractionPhysicsDispatcher'])+
+	sect('Rendering','',['OpenGLRenderingEngine','GlShapeFunctor','GlStateFunctor','GlBoundFunctor','GlInteractionGeometryFunctor','GlInteractionPhysicsFunctor']), # ,'GlShapeDispatcher','GlStateDispatcher','GlBoundDispatcher','GlInteractionGeometryDispatcher','GlInteractionPhysicsDispatcher'])+
 	sect('Simulation data','',['Omega','BodyContainer','InteractionContainer','ForceContainer','MaterialContainer'])
 	+"""
 Other classes

=== modified file 'gui/SConscript'
--- gui/SConscript	2010-06-07 14:56:29 +0000
+++ gui/SConscript	2010-07-13 19:58:14 +0000
@@ -2,7 +2,7 @@
 Import('*')
 linkPlugins=env['linkPlugins']
 
-if 'opengl' in env['features']:
+if 'opengl' in env['features'] and not 'noqt3' in env['features']:
 	env.Install('$PREFIX/lib/yade$SUFFIX/gui',[
 		env.SharedLibrary('QtGUI',
 			['qt3/FileDialog.cpp',

=== modified file 'lib/SConscript'
--- lib/SConscript	2010-06-07 14:56:29 +0000
+++ lib/SConscript	2010-07-13 19:58:14 +0000
@@ -9,8 +9,9 @@
 	#return env.Install('$PREFIX/lib/yade$SUFFIX/lib',env.StaticLibrary(*args,**kw))
 
 if 'opengl' in env['features']:
-	yadeStaticOrSharedLib('yade-serialization-qt',['serialization-qt/QtGUIGenerator.cpp'],LIBS=['yade-support'])
-	yadeStaticOrSharedLib('yade-opengl',env.Combine('yade-opengl.cpp',['opengl/FpsTracker.cpp','opengl/GLTextLabel.cpp','opengl/GLWindow.cpp','opengl/GLWindowsManager.cpp','opengl/GLUtils.cpp']),LIBS=env['LIBS']+['glut','GL','qglviewer-qt3']),
+	yadeStaticOrSharedLib('yade-opengl',env.Combine('yade-opengl.cpp',['opengl/FpsTracker.cpp','opengl/GLTextLabel.cpp','opengl/GLWindow.cpp','opengl/GLWindowsManager.cpp','opengl/GLUtils.cpp']),LIBS=env['LIBS']+['glut','GL','GLU']),
+	if not 'noqt3' in env['features']:
+		yadeStaticOrSharedLib('yade-serialization-qt',['serialization-qt/QtGUIGenerator.cpp'],LIBS=['yade-support'])
 
 yadeStaticOrSharedLib('yade-support',[
 	env.Combine('yade-support.cpp',['base/Math.cpp']+

=== modified file 'lib/opengl/GLUtils.cpp'
--- lib/opengl/GLUtils.cpp	2010-05-04 13:56:05 +0000
+++ lib/opengl/GLUtils.cpp	2010-07-13 19:58:14 +0000
@@ -11,3 +11,47 @@
 		glVertex3v(Vector3r(a+c)); glVertex3v(Vector3r(a+b+c));
 	glEnd();
 }
+
+/****
+ code copied over from qglviewer
+****/
+
+/*! Draws a 3D arrow along the positive Z axis.
+
+\p length, \p radius and \p nbSubdivisions define its geometry. If \p radius is negative
+(default), it is set to 0.05 * \p length.
+
+Use drawArrow(const Vec& from, const Vec& to, float radius, int nbSubdivisions) or change the \c
+ModelView matrix to place the arrow in 3D.
+
+Uses current color and does not modify the OpenGL state. */
+void GLUtils::QGLViewer::drawArrow(float length, float radius, int nbSubdivisions)
+{
+	static GLUquadric* quadric = gluNewQuadric();
+
+	if (radius < 0.0)
+		radius = 0.05 * length;
+
+	const float head = 2.5*(radius / length) + 0.1;
+	const float coneRadiusCoef = 4.0 - 5.0 * head;
+
+	gluCylinder(quadric, radius, radius, length * (1.0 - head/coneRadiusCoef), nbSubdivisions, 1);
+	glTranslatef(0.0, 0.0, length * (1.0 - head));
+	gluCylinder(quadric, coneRadiusCoef * radius, 0.0, head * length, nbSubdivisions, 1);
+	glTranslatef(0.0, 0.0, -length * (1.0 - head));
+}
+
+/*! Draws a 3D arrow between the 3D point \p from and the 3D point \p to, both defined in the
+current ModelView coordinates system.
+
+See drawArrow(float length, float radius, int nbSubdivisions) for details. */
+void GLUtils::QGLViewer::drawArrow(const Vector3r& from, const Vector3r& to, float radius, int nbSubdivisions)
+{
+	glPushMatrix();
+	glTranslatef(from[0],from[1],from[2]);
+	Quaternionr q(Quaternionr().setFromTwoVectors(Vector3r(0,0,1),to-from));
+	glMultMatrixd(q.toRotationMatrix().data());
+	drawArrow((to-from).norm(), radius, nbSubdivisions);
+	glPopMatrix();
+}
+

=== modified file 'lib/opengl/GLUtils.hpp'
--- lib/opengl/GLUtils.hpp	2010-06-07 14:56:29 +0000
+++ lib/opengl/GLUtils.hpp	2010-07-13 19:58:14 +0000
@@ -5,17 +5,21 @@
 #pragma once
 
 #include<yade/lib-opengl/OpenGLWrapper.hpp>
-#include<QGLViewer/qglviewer.h>
 #include<boost/lexical_cast.hpp>
 #include<sstream>
 #include<iomanip>
 #include<string>
 
 struct GLUtils{
+	// code copied from qglviewer
+	struct QGLViewer{
+		static void drawArrow(float length=1.0f, float radius=-1.0f, int nbSubdivisions=12);
+		static void drawArrow(const Vector3r& from, const Vector3r& to, float radius=-1.0f, int nbSubdivisions=12);
+	};
 	// render wire of parallelepiped with sides given by vectors a,b,c; zero corner is at origin
 	static void Parallelepiped(const Vector3r& a, const Vector3r& b, const Vector3r& c);
 	static void GLDrawArrow(const Vector3r& from, const Vector3r& to, const Vector3r& color=Vector3r(1,1,1)){
-		glEnable(GL_LIGHTING); glColor3v(color); qglviewer::Vec a(from[0],from[1],from[2]),b(to[0],to[1],to[2]); QGLViewer::drawArrow(a,b);	
+		glEnable(GL_LIGHTING); glColor3v(color); QGLViewer::drawArrow(from,to);	
 	}
 	static void GLDrawLine(const Vector3r& from, const Vector3r& to, const Vector3r& color=Vector3r(1,1,1)){
 		glEnable(GL_LIGHTING); glColor3v(color);

=== modified file 'lib/serialization/Serializable.hpp'
--- lib/serialization/Serializable.hpp	2010-06-12 20:39:00 +0000
+++ lib/serialization/Serializable.hpp	2010-07-13 19:58:14 +0000
@@ -181,7 +181,7 @@
 	// use later: void must_use_both_YADE_CLASS_BASE_DOC_ATTRS_and_YADE_PLUGIN(); 
 // #define YADE_CLASS_BASE_DOC_ATTRS_PY(thisClass,baseClass,docString,attrs,extras) YADE_CLASS_BASE_DOC_ATTRS_DEPREC_PY(thisClass,baseClass,docString,attrs,,extras)
 
-#define _STRIPDECL4(x,y,z) (( BOOST_PP_TUPLE_ELEM(4,1,z),BOOST_PP_TUPLE_ELEM(4,3,z) " :ydefault:`" BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(4,2,z)) "`" ))
+#define _STRIPDECL4(x,y,z) (( BOOST_PP_TUPLE_ELEM(4,1,z),BOOST_PP_TUPLE_ELEM(4,3,z) " :ydefault:`" BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(4,2,z)) "`" " :yattrtype:`" BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(4,0,z)) "`" ))
 // return type name; (for declaration)
 #define _ATTR_DECL(x,y,z) BOOST_PP_TUPLE_ELEM(4,0,z) BOOST_PP_TUPLE_ELEM(4,1,z);
 // return name(default), (for initializers list); TRICKY: last one must not have the comma
@@ -196,8 +196,8 @@
 	thisClass() BOOST_PP_IF(BOOST_PP_SEQ_SIZE(inits attrDecls),:,) BOOST_PP_SEQ_FOR_EACH_I(_ATTR_INI,BOOST_PP_DEC(BOOST_PP_SEQ_SIZE(inits attrDecls)), inits BOOST_PP_SEQ_FOR_EACH(_DECLINI4,~,attrDecls)) { ctor ; } /* ctor, with initialization of defaults */ \
 	_YADE_CLASS_BASE_DOC_ATTRS_DEPREC_PY(thisClass,baseClass,docString,BOOST_PP_SEQ_FOR_EACH(_STRIPDECL4,~,attrDecls),deprec,extras)
 
-#define _DEF_READWRITE_STATIC(thisClass,attr,doc)
-#define _STATATTR_PY(x,thisClass,z) _DEF_READWRITE_CUSTOM_STATIC(thisClass,BOOST_PP_TUPLE_ELEM(4,1,z),/*docstring*/ "|ystatic| :ydefault:`" BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(4,2,z)) "` " BOOST_PP_TUPLE_ELEM(4,3,z))
+#define _STAT_NONSTAT_ATTR_PY(thisClass,attr,doc) /* _DEF_READWRITE_CUSTOM_STATIC(thisClass,attr,doc) */  _DEF_READWRITE_CUSTOM(thisClass,attr,doc) /* duplicate static and non-static attributes do not work (they apparently trigger to-python converter being added; for now, make then non-static, that's it. */
+#define _STATATTR_PY(x,thisClass,z) _STAT_NONSTAT_ATTR_PY(thisClass,BOOST_PP_TUPLE_ELEM(4,1,z),/*docstring*/ "|ystatic| :ydefault:`" BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(4,2,z)) "` :yattrtype:`" BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(4,0,z)) "` " BOOST_PP_TUPLE_ELEM(4,3,z))
 #define _STATATTR_DECL(x,y,z) static BOOST_PP_TUPLE_ELEM(4,0,z) BOOST_PP_TUPLE_ELEM(4,1,z);
 #define _STRIP_TYPE_DEFAULT_DOC(x,y,z) (BOOST_PP_TUPLE_ELEM(4,1,z))
 #define _STATATTR_SET(x,thisClass,z) thisClass::BOOST_PP_TUPLE_ELEM(4,1,z)=BOOST_PP_TUPLE_ELEM(4,2,z);

=== modified file 'pkg/common/RenderingEngine/OpenGLRenderingEngine.hpp'
--- pkg/common/RenderingEngine/OpenGLRenderingEngine.hpp	2010-07-12 17:36:42 +0000
+++ pkg/common/RenderingEngine/OpenGLRenderingEngine.hpp	2010-07-13 19:58:14 +0000
@@ -46,14 +46,6 @@
 		// updated after every call to render
 		shared_ptr<Scene> scene; 
 
-		#if 0
-		DynLibDispatcher<InteractionGeometry, GlInteractionGeometryFunctor, void, TYPELIST_5(const shared_ptr<InteractionGeometry>&, const shared_ptr<Interaction>&, const shared_ptr<Body>&, const shared_ptr<Body>&, bool)> interactionGeometryDispatcher;
-		DynLibDispatcher<InteractionPhysics, GlInteractionPhysicsFunctor, void, TYPELIST_5(const shared_ptr<InteractionPhysics>&, const shared_ptr<Interaction>&, const shared_ptr<Body>&, const shared_ptr<Body>&, bool)> interactionPhysicsDispatcher;
-
-		DynLibDispatcher<State, GlStateFunctor, void, TYPELIST_1(const shared_ptr<State>&)> stateDispatcher;
-		DynLibDispatcher<Bound, GlBoundFunctor, void, TYPELIST_2(const shared_ptr<Bound>&, Scene*)> boundDispatcher;
-		DynLibDispatcher<Shape, GlShapeFunctor, void, TYPELIST_4(const shared_ptr<Shape>&, const shared_ptr<State>&,bool,const GLViewInfo&)> shapeDispatcher;
-		#endif
 		GlBoundDispatcher boundDispatcher;
 		GlInteractionGeometryDispatcher interactionGeometryDispatcher;
 		GlInteractionPhysicsDispatcher interactionPhysicsDispatcher;
@@ -80,6 +72,7 @@
 		void init();
 		void initgl();
 		void render(const shared_ptr<Scene>& scene, body_id_t selection = body_id_t(-1));
+		void pyRender(){render(Omega::instance().getScene());}
 		void renderWithNames(const shared_ptr<Scene>& );
 	
 	private :
@@ -130,7 +123,8 @@
 		/*init*/,
 		/*ctor*/,
 		/*py*/
-		.def("setRefSe3",&OpenGLRenderingEngine::setBodiesRefSe3,"Make current positions and orientation reference for scaleDisplacements and scaleRotations.");
+		.def("setRefSe3",&OpenGLRenderingEngine::setBodiesRefSe3,"Make current positions and orientation reference for scaleDisplacements and scaleRotations.")
+		.def("render",&OpenGLRenderingEngine::pyRender,"Render the scene in the current OpenGL context.")
 	);
 };
 REGISTER_SERIALIZABLE(OpenGLRenderingEngine);

=== modified file 'py/yadeWrapper/customConverters.cpp'
--- py/yadeWrapper/customConverters.cpp	2010-07-12 17:36:42 +0000
+++ py/yadeWrapper/customConverters.cpp	2010-07-13 19:58:14 +0000
@@ -6,6 +6,7 @@
 // boost::python
 #if 0
 	#include<indexing_suite/container_suite.hpp>
+	// #include<indexing_suite/container_proxy.hpp>
 	#include<indexing_suite/vector.hpp>
 #endif
 
@@ -112,13 +113,35 @@
 	}
 };
 
+#if 0
+template<typename T>
+std::string vectorRepr(const vector<T>& v){ std::string ret("["); for(size_t i=0; i<v.size(); i++) { if(i>0) ret+=","; ret+=lexical_cast<string>(v[i]); } return ret+"]"; }
+template<>
+std::string vectorRepr(const vector<std::string>& v){ std::string ret("["); for(size_t i=0; i<v.size(); i++) { if(i>0) ret+=","; ret+="'"+lexical_cast<string>(v[i])+"'"; } return ret+"]"; }
 
+// is not picked up?
+bool operator<(const Vector3r& a, const Vector3r& b){ return a[0]<b[0]; }
+#endif
 
 
 using namespace boost::python;
 
 BOOST_PYTHON_MODULE(_customConverters){
-	// class_<std::vector<int> >("vecInt").def(indexing::container_suite<std::vector<int> >());
+
+	// syntax ??
+	//   http://language-binding.net/pyplusplus/documentation/indexing_suite_v2.html.html#container_proxy
+	//   http://www.mail-archive.com/cplusplus-sig@xxxxxxxxxx/msg00862.html
+	//class_<indexing::container_proxy<std::vector<string> >,bases<class_<std::vector<string> > > >("vecStr").def(indexing::container_suite<indexing::container_proxy<std::vector<string> > >());
+	//class_<std::vector<string> >("vecStr").def(indexing::container_suite<std::vector<string> >());
+
+	#if 0
+		custom_vector_from_seq<string>(); class_<std::vector<string> >("vector_" "string").def(indexing::container_suite<std::vector<string> >()).def("__repr__",&vectorRepr<string>);
+		custom_vector_from_seq<int>(); class_<std::vector<int> >("vector_" "int").def(indexing::container_suite<std::vector<int> >()).def("__repr__",&vectorRepr<int>);
+		custom_vector_from_seq<Real>(); class_<std::vector<Real> >("vector_" "Real").def(indexing::container_suite<std::vector<Real> >()).def("__repr__",&vectorRepr<Real>);
+		// this needs operator< for Vector3r (defined above, but not picked up for some reason)
+		custom_vector_from_seq<Vector3r>(); class_<std::vector<Vector3r> >("vector_" "Vector3r").def(indexing::container_suite<std::vector<Vector3r> >()).def("__repr__",&vectorRepr<Vector3r>);
+	#endif
+
 	custom_Se3r_from_seq(); to_python_converter<Se3r,custom_se3_to_tuple>();
 
 	// StrArrayMap (typedef for std::map<std::string,numpy_boost>) → python dictionary
@@ -126,7 +149,7 @@
 	// register from-python converter and to-python converter
 
 	// register 2-way conversion between c++ vector and python homogeneous sequence (list/tuple) of corresponding type
-	#define VECTOR_SEQ_CONV(Type) custom_vector_from_seq<Type>(); to_python_converter<std::vector<Type>, custom_vector_to_list<Type> >();
+	#define VECTOR_SEQ_CONV(Type) custom_vector_from_seq<Type>();  to_python_converter<std::vector<Type>, custom_vector_to_list<Type> >();
 		VECTOR_SEQ_CONV(int);
 		VECTOR_SEQ_CONV(Real);
 		VECTOR_SEQ_CONV(Se3r);

=== modified file 'scripts/test/chained-cylinder-spring.py'
--- scripts/test/chained-cylinder-spring.py	2010-07-13 19:14:55 +0000
+++ scripts/test/chained-cylinder-spring.py	2010-07-13 19:58:14 +0000
@@ -38,7 +38,7 @@
 ]
 
 #Generate a spiral
-Ne=200
+Ne=400
 for i in range(0, Ne):
 	omega=60.0/float(Ne); hy=0.10; hz=0.15;
 	px=float(i)*(omega/60.0); py=sin(float(i)*omega)*hy; pz=cos(float(i)*omega)*hz;
@@ -67,5 +67,6 @@
 
 #yade.qt.Renderer().bound=True
 plot.plot()
+O.saveTmp()
 #O.bodies[0].state.angVel=Vector3(0.05,0,0)
 

=== added file 'scripts/test/qt4-attributes.py'
--- scripts/test/qt4-attributes.py	1970-01-01 00:00:00 +0000
+++ scripts/test/qt4-attributes.py	2010-07-13 19:58:14 +0000
@@ -0,0 +1,119 @@
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+#from PyQt4 import Qwt5
+
+import re
+import logging
+logging.basicConfig(level=logging.DEBUG)
+from logging import debug,info,warning,error
+
+class SerializableData(QWidget):
+	"Class displaying and modifying serializable attributes of a yade object."
+	import collections
+	# each attribute has one entry associated with itself
+	class EntryData:
+		def __init__(self,name,T):
+			self.name,self.T=name,T
+			self.lineNo,self.widget=None,None
+	def __init__(self,ser,parent=None):
+		"Construct window, *ser* is the object we want to show."
+		QWidget.__init__(self,parent)
+		self.ser=ser
+		self.entries=[]
+		print self.ser
+		debug('New Serializable of type %s'%ser.__class__.__name__)
+		self.setWindowTitle(str(ser))
+		self.mkWidgets()
+	def getListTypeFromDocstring(self,attr):
+		"Guess type of array by scanning docstring for :yattrtype: and parsing its argument; ugly, but works."
+		doc=getattr(self.ser.__class__,attr).__doc__
+		if doc==None:
+			error("Attribute %s has no docstring."%attr)
+			return None
+		m=re.search(r':yattrtype:`([^`]*)`',doc)
+		if not m:
+			error("Attribute %s does not contain :yattrtype:`....` (docstring is '%s'"%(attr,doc))
+			return None
+		cxxT=m.group(1)
+		#debug('Got type "%s" from :yattrtype:'%cxxT)
+		def vecTest(T,cxxT):
+			regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(std\s*::)?\s*('+T+r')\s*>\s*$'
+			m=re.match(regexp,cxxT)
+			return m
+		vecMap={
+			'int':int,'long':int,'body_id_t':long,'size_t':long,
+			'Real':float,'float':float,'double':float,
+			'Vector3r':Vector3,'Matrix3r':Matrix3,
+			'string':str
+		}
+		for T,ret in vecMap.items():
+			if vecTest(T,cxxT):
+				debug("Got type %s from cxx type %s"%(ret.__name__,cxxT))
+				return (ret,)
+		error("Unable to guess python type from cxx type '%s'"%cxxT)
+		return None
+	def mkAttrEntries(self):
+		d=self.ser.dict()
+		for attr,val in self.ser.dict().items():
+			if isinstance(val,list):
+				if len(val)==0: t=self.getListTypeFromDocstring(attr)
+				else: t=(val[0].__class__,) # 1-tuple is list of the contained type
+			else: t=val.__class__
+			debug('Attr %s is of type %s'%(attr,((t[0].__name__,) if isinstance(t,tuple) else t.__name__)))
+			self.entries.append(self.EntryData(name=attr,T=t))
+	def mkWidget(self,entry):
+		self.chgPalette=QPalette()
+		self.chgPalette.setColor(QPalette.Window,QColor('red'))
+		widget=None
+		if entry.T==bool:
+			widget=QCheckBox('',self)
+			widget.setChecked(getattr(self.ser,entry.name))
+			self.connect(widget,SIGNAL('stateChanged(int)'),lambda num: setattr(self.ser,entry.name,(True if num>0 else False)))
+		elif entry.T==str:
+			widget=QLineEdit('',self)
+			widget.setText(getattr(self.ser,entry.name))
+			self.connect(widget,SIGNAL('editingFinished()'),lambda: setattr(self.ser,entry.name,str(widget.text())))
+		elif entry.T==int:
+			widget=QSpinBox(self)
+			widget.setRange(int(-1e10),int(1e10)); widget.setSingleStep(1);
+			widget.setValue(getattr(self.ser,entry.name))
+			self.connect(widget,SIGNAL('valueChanged(int)'),lambda val: setattr(self.ser,entry.name,val))
+		elif entry.T==float:
+			widget=QLineEdit('',self)
+			widget.setText(str(getattr(self.ser,entry.name)))
+			self.connect(widget,SIGNAL('editingFinished()'),lambda val: setattr(self.ser,entry.name,float(str(widget.text()))))
+		elif entry.T==Vector3:
+			widget=QFrame(self); widget.setContentsMargins(0,0,0,0)
+			box=QHBoxLayout(widget); box.setSpacing(0)
+			vec=getattr(self.ser,entry.name)
+			for i in range(3):
+				subw=QLineEdit('')
+				subw.setText(str(vec[i]))
+				subw.setSizePolicy(QSizePolicy().setHorizontalPolicy(
+				class updateVec:
+					"bind local args... ugly"
+					def __init__(self,i,ser,name,subw): self.i,self.ser,self.name,self.subw=i,ser,name,subw
+					def __call__(self): 
+						newVal=float(str(self.subw.text()))
+						debug('updating %d with %s'%(self.i,newVal))
+						v=getattr(self.ser,self.name); v[self.i]=newVal; setattr(self.ser,self.name,v)
+				self.connect(subw,SIGNAL('editingFinished()'), updateVec(i,self.ser,entry.name,subw))
+				box.addWidget(subw)
+		return widget
+	def mkWidgets(self):
+		self.mkAttrEntries()
+		grid=QtGui.QGridLayout()
+		for lineNo,entry in enumerate(self.entries):
+			grid.addWidget(QLabel(entry.name),lineNo,0)
+			entry.widget=self.mkWidget(entry)
+			if entry.widget: grid.addWidget(entry.widget,lineNo,1)
+			else: grid.addWidget(QLabel('<i>unhandled type</i>'),lineNo,1)
+		self.setLayout(grid)
+
+
+
+
+s1=SerializableData(VTKRecorder())
+s2=SerializableData(OpenGLRenderingEngine())
+s1.show()
+s2.show()

=== added file 'scripts/test/qt4-pyqglviewer.py'
--- scripts/test/qt4-pyqglviewer.py	1970-01-01 00:00:00 +0000
+++ scripts/test/qt4-pyqglviewer.py	2010-07-13 19:58:14 +0000
@@ -0,0 +1,21 @@
+# install pyqglviewer from https://launchpad.net/~nakednous/+ppa-packages
+from PyQGLViewer import *
+
+class Viewer(QGLViewer):
+	def __init__(self):
+		QGLViewer.__init__(self)
+		self.renderer=OpenGLRenderingEngine()
+	def init(self):
+		self.setAxisIsDrawn(True)
+		self.setGridIsDrawn(True)
+	def draw(self):
+		self.renderer.render()
+
+O.bodies.append([utils.sphere((0,0,-.4),.3),utils.sphere((0,0,.4),.3)])
+
+viewer=Viewer()
+viewer.setWindowTitle('Yade')
+viewer.show()
+
+
+